Skip to content

Commit

Permalink
Merge pull request #13 from SiaFoundation/chris/public-key-serialize
Browse files Browse the repository at this point in the history
Implement Serialize for multiple types
  • Loading branch information
n8maninger authored Apr 29, 2024
2 parents 06325a1 + ed3edd9 commit a34bfb2
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 118 deletions.
62 changes: 47 additions & 15 deletions src/address.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use core::fmt;
use std::io::{Error, Write};

use crate::blake2b::{Accumulator, LEAF_HASH_PREFIX};
use crate::{specifier::Specifier, HexParseError, PublicKey, SiaEncodable, UnlockKey};
use crate::encoding::to_writer;
use crate::{specifier::Specifier, HexParseError, PublicKey, UnlockKey};
use blake2b_simd::Params;
use serde::Serialize;

Expand Down Expand Up @@ -116,24 +116,14 @@ impl Serialize for Algorithm {
}

// specifies the conditions for spending an output or revising a file contract.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UnlockConditions {
pub timelock: u64,
pub public_keys: Vec<UnlockKey>,
pub required_signatures: u64,
}

impl SiaEncodable for UnlockConditions {
fn encode<W: Write>(&self, w: &mut W) -> Result<(), Error> {
w.write_all(&self.timelock.to_le_bytes())?;
w.write_all(&(self.public_keys.len() as u64).to_le_bytes())?;
for key in &self.public_keys {
key.encode(w)?;
}
w.write_all(&self.required_signatures.to_le_bytes())
}
}

impl UnlockConditions {
pub fn new(
timelock: u64,
Expand Down Expand Up @@ -172,7 +162,7 @@ impl UnlockConditions {
for key in &self.public_keys {
let mut state = Params::new().hash_length(32).to_state();
state.update(LEAF_HASH_PREFIX);
key.encode(&mut state).unwrap();
to_writer(&mut state, key).unwrap();

let h = state.finalize();
let mut leaf = [0u8; 32];
Expand Down Expand Up @@ -247,6 +237,48 @@ mod tests {
)
}

#[test]
fn test_json_serialize_unlock_conditions() {
let uc = UnlockConditions::new(
123,
vec![UnlockKey::new(
Algorithm::ED25519,
PublicKey::new([
0x9a, 0xac, 0x1f, 0xfb, 0x1c, 0xfd, 0x10, 0x79, 0xa8, 0xc6, 0xc8, 0x7b, 0x47,
0xda, 0x1d, 0x56, 0x7e, 0x35, 0xb9, 0x72, 0x34, 0x99, 0x3c, 0x28, 0x8c, 0x1a,
0xd0, 0xdb, 0x1d, 0x1c, 0xe1, 0xb6,
]),
)],
1,
);
assert_eq!(serde_json::to_string(&uc).unwrap(), "{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"requiredSignatures\":1}")
}

#[test]
fn test_serialize_unlock_conditions() {
let uc = UnlockConditions::new(
123,
vec![UnlockKey::new(
Algorithm::ED25519,
PublicKey::new([
0x9a, 0xac, 0x1f, 0xfb, 0x1c, 0xfd, 0x10, 0x79, 0xa8, 0xc6, 0xc8, 0x7b, 0x47,
0xda, 0x1d, 0x56, 0x7e, 0x35, 0xb9, 0x72, 0x34, 0x99, 0x3c, 0x28, 0x8c, 0x1a,
0xd0, 0xdb, 0x1d, 0x1c, 0xe1, 0xb6,
]),
)],
1,
);
assert_eq!(
to_bytes(&uc).unwrap(),
[
123, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 101, 100, 50, 53, 53, 49, 57, 0,
0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 154, 172, 31, 251, 28, 253, 16,
121, 168, 198, 200, 123, 71, 218, 29, 86, 126, 53, 185, 114, 52, 153, 60, 40, 140,
26, 208, 219, 29, 28, 225, 182, 1, 0, 0, 0, 0, 0, 0, 0
]
)
}

#[test]
fn test_standard_unlockhash() {
let test_cases = vec![
Expand Down
34 changes: 28 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub use address::*;
pub use consensus::*;
pub use currency::*;
pub use seed::*;
use serde::Serialize;
pub use signing::*;
pub use spendpolicy::*;
pub use transactions::*;
Expand All @@ -39,6 +40,16 @@ pub enum HexParseError {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Hash256([u8; 32]);

impl Serialize for Hash256 {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
} else {
self.0.serialize(serializer)
}
}
}

impl Hash256 {
pub fn from_slice(data: [u8; 32]) -> Self {
Hash256(data)
Expand Down Expand Up @@ -70,14 +81,25 @@ impl fmt::Display for Hash256 {
}
}

impl SiaEncodable for Hash256 {
fn encode<W: Write>(&self, w: &mut W) -> Result<(), Error> {
w.write_all(&self.0)
}
}

impl AsRef<[u8]> for Hash256 {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_json_serialize_hash256() {
let hash = Hash256::parse_string(
"h:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6",
)
.unwrap();
assert_eq!(
serde_json::to_string(&hash).unwrap(),
"\"h:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\""
);
}
}
140 changes: 110 additions & 30 deletions src/signing.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
use core::fmt;
use std::io::{Error, Write};
use std::time::SystemTime;

use crate::consensus::ChainIndex;
use crate::encoding::to_writer;
use crate::transactions::{CoveredFields, Transaction};
use crate::{Algorithm, HexParseError, SiaEncodable};
use blake2b_simd::Params;
use ed25519_dalek::{Signature as ED25519Signature, Signer, SigningKey, Verifier, VerifyingKey};
use serde::ser::SerializeStruct;
use serde::Serialize;
use serde_json::to_writer;

/// An ed25519 public key that can be used to verify a signature
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct PublicKey([u8; 32]);

impl Serialize for PublicKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
} else {
serializer.serialize_bytes(&self.0)
}
}
}

impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
hex::encode(self.0).fmt(f)
}
}

impl PublicKey {
pub fn new(buf: [u8; 32]) -> Self {
PublicKey(buf)
Expand Down Expand Up @@ -81,6 +98,25 @@ pub struct UnlockKey {
public_key: PublicKey,
}

impl Serialize for UnlockKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
} else {
let mut s = serializer.serialize_struct("UnlockKey", 2)?;
s.serialize_field("algorithm", &self.algorithm)?;
s.serialize_field("public_key", &self.public_key)?;
s.end()
}
}
}

impl fmt::Display for UnlockKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.algorithm, self.public_key)
}
}

impl UnlockKey {
/// Creates a new UnlockKey
pub fn new(algorithm: Algorithm, public_key: PublicKey) -> UnlockKey {
Expand Down Expand Up @@ -113,25 +149,6 @@ impl UnlockKey {
}
}

impl fmt::Display for UnlockKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}:{}",
self.algorithm,
hex::encode(self.public_key.as_ref())
)
}
}

impl SiaEncodable for UnlockKey {
fn encode<W: Write>(&self, w: &mut W) -> Result<(), Error> {
to_writer(w, &self.algorithm).unwrap(); // TODO: handle error
w.write_all(&32_u64.to_le_bytes())?;
w.write_all(self.public_key.as_ref())
}
}

impl Drop for PrivateKey {
fn drop(&mut self) {
// Zero out the private key
Expand All @@ -154,6 +171,16 @@ pub struct NetworkHardforks {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature([u8; 64]);

impl Serialize for Signature {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
serializer.serialize_str(&self.to_string())
} else {
serializer.serialize_bytes(&self.0) // prefixed with length
}
}
}

impl Signature {
pub fn new(sig: [u8; 64]) -> Self {
Signature(sig)
Expand Down Expand Up @@ -186,13 +213,6 @@ impl AsRef<[u8; 64]> for Signature {
}
}

impl SiaEncodable for Signature {
fn encode<W: Write>(&self, w: &mut W) -> Result<(), Error> {
w.write_all(&(self.0.len() as u64).to_le_bytes())?;
w.write_all(&self.0)
}
}

impl fmt::Display for Signature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "sig:{}", hex::encode(self.0))
Expand Down Expand Up @@ -290,8 +310,8 @@ impl SigningState {
state.update(&public_key_index.to_le_bytes());
state.update(&timelock.to_le_bytes());

for i in covered_sigs.into_iter() {
txn.signatures[i as usize].encode(&mut state).unwrap();
for i in covered_sigs.iter() {
to_writer(&mut state, i).unwrap();
}

state.finalize().as_bytes().try_into().unwrap()
Expand Down Expand Up @@ -351,6 +371,66 @@ mod tests {
use super::*;
use crate::*;

#[test]
fn test_json_serialize_public_key() {
assert_eq!(
serde_json::to_string(&PublicKey::new([
0x9a, 0xac, 0x1f, 0xfb, 0x1c, 0xfd, 0x10, 0x79, 0xa8, 0xc6, 0xc8, 0x7b, 0x47, 0xda,
0x1d, 0x56, 0x7e, 0x35, 0xb9, 0x72, 0x34, 0x99, 0x3c, 0x28, 0x8c, 0x1a, 0xd0, 0xdb,
0x1d, 0x1c, 0xe1, 0xb6,
]))
.unwrap(),
"\"9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\""
);
}

#[test]
fn test_json_serialize_unlock_key() {
assert_eq!(
serde_json::to_string(
&UnlockKey::parse_string(
"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6"
)
.unwrap()
)
.unwrap(),
"\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\""
);
}

#[test]
fn test_json_serialize_signature() {
assert_eq!(
serde_json::to_string(
&Signature::parse_string(
"sig:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b69aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6"
)
.unwrap()
)
.unwrap(),
"\"sig:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b69aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\""
);
}

#[test]
fn test_serialize_public_key() {
let key: [u8; 32] = [
0x9a, 0xac, 0x1f, 0xfb, 0x1c, 0xfd, 0x10, 0x79, 0xa8, 0xc6, 0xc8, 0x7b, 0x47, 0xda,
0x1d, 0x56, 0x7e, 0x35, 0xb9, 0x72, 0x34, 0x99, 0x3c, 0x28, 0x8c, 0x1a, 0xd0, 0xdb,
0x1d, 0x1c, 0xe1, 0xb6,
];
let pk = UnlockKey::new(Algorithm::ED25519, PublicKey::new(key));
assert_eq!(
&encoding::to_bytes(&pk).unwrap(),
&[
0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0xac, 0x1f, 0xfb,
0x1c, 0xfd, 0x10, 0x79, 0xa8, 0xc6, 0xc8, 0x7b, 0x47, 0xda, 0x1d, 0x56, 0x7e, 0x35,
0xb9, 0x72, 0x34, 0x99, 0x3c, 0x28, 0x8c, 0x1a, 0xd0, 0xdb, 0x1d, 0x1c, 0xe1, 0xb6
]
);
}

#[test]
fn test_whole_sig_hash() {
let state = SigningState {
Expand Down
6 changes: 5 additions & 1 deletion src/spendpolicy.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use blake2b_simd::Params;
use core::{fmt, slice::Iter};
use serde_json::to_writer;
use sha2::{Digest, Sha256};
use std::{
io::{Error as IOError, Write},
Expand Down Expand Up @@ -259,7 +260,10 @@ impl SpendPolicy {
}
SpendPolicy::Opaque(addr) => w.write_all(addr.as_ref()),
#[allow(deprecated)]
SpendPolicy::UnlockConditions(uc) => uc.encode(w),
SpendPolicy::UnlockConditions(uc) => {
to_writer(w, uc).unwrap();
Ok(())
}
}
}
}
Expand Down
Loading

0 comments on commit a34bfb2

Please sign in to comment.