diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 5788e30..7e1a1ad 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -14,3 +14,4 @@ secp256k1 = "0.31.1" [dev-dependencies] test-program-methods = { path = "test_program_methods" } +hex = "0.4.3" diff --git a/nssa/src/public_transaction/encoding.rs b/nssa/src/public_transaction/encoding.rs index 01483a5..eb7fbfc 100644 --- a/nssa/src/public_transaction/encoding.rs +++ b/nssa/src/public_transaction/encoding.rs @@ -11,9 +11,9 @@ const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"NSSA/v0.1/ impl Message { /// Serializes a `Message` into bytes in the following layout: - /// TAG || (bytes LE) * 8 || addresses_len (4 bytes LE) || addresses (32 bytes * N) || nonces_len (4 bytes LE) || nonces (16 bytes * M) || instruction_data_len || instruction_data (4 bytes * K) + /// PREFIX || (4 bytes LE) * 8 || addresses_len (4 bytes LE) || addresses (32 bytes * N) || nonces_len (4 bytes LE) || nonces (16 bytes LE * M) || instruction_data_len || instruction_data (4 bytes LE * K) /// Integers and words are encoded in little-endian byte order, and fields appear in the above order. - pub(crate) fn message_to_bytes(&self) -> Vec { + pub(crate) fn to_bytes(&self) -> Vec { let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec(); // program_id: [u32; 8] for word in &self.program_id { @@ -27,6 +27,7 @@ impl Message { bytes.extend_from_slice(addr.value()); } // nonces: Vec + // serialize length as u32 little endian, then all nonces concatenated in LE let nonces_len = self.nonces.len() as u32; bytes.extend(&nonces_len.to_le_bytes()); for nonce in &self.nonces { @@ -93,8 +94,8 @@ impl WitnessSet { let size = self.signatures_and_public_keys.len() as u32; bytes.extend_from_slice(&size.to_le_bytes()); for (signature, public_key) in &self.signatures_and_public_keys { - bytes.extend_from_slice(&signature.value); - bytes.extend_from_slice(&public_key.0); + bytes.extend_from_slice(signature.to_bytes()); + bytes.extend_from_slice(public_key.to_bytes()); } bytes } @@ -120,7 +121,7 @@ impl WitnessSet { impl PublicTransaction { pub fn to_bytes(&self) -> Vec { - let mut bytes = self.message.message_to_bytes(); + let mut bytes = self.message.to_bytes(); bytes.extend_from_slice(&self.witness_set.to_bytes()); bytes } diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index 7d8b5bc..83edf66 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -11,7 +11,7 @@ pub struct WitnessSet { impl WitnessSet { pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self { - let message_bytes = message.message_to_bytes(); + let message_bytes = message.to_bytes(); let signatures_and_public_keys = private_keys .iter() .map(|&key| (Signature::new(key, &message_bytes), PublicKey::new(key))) @@ -22,7 +22,7 @@ impl WitnessSet { } pub fn is_valid_for(&self, message: &Message) -> bool { - let message_bytes = message.message_to_bytes(); + let message_bytes = message.to_bytes(); for (signature, public_key) in self.iter_signatures() { if !signature.is_valid_for(&message_bytes, &public_key) { return false; diff --git a/nssa/src/signature/encoding.rs b/nssa/src/signature/encoding.rs index 005cea9..a7f112e 100644 --- a/nssa/src/signature/encoding.rs +++ b/nssa/src/signature/encoding.rs @@ -10,6 +10,10 @@ impl PublicKey { cursor.read_exact(&mut value).unwrap(); Self(value) } + + pub(crate) fn to_bytes(&self) -> &[u8] { + &self.0 + } } impl Signature { @@ -19,4 +23,8 @@ impl Signature { cursor.read_exact(&mut value).unwrap(); Self { value } } + + pub(crate) fn to_bytes(&self) -> &[u8] { + &self.value + } } diff --git a/nssa/src/signature/public_key.rs b/nssa/src/signature/public_key.rs index f7a8c25..830f6b1 100644 --- a/nssa/src/signature/public_key.rs +++ b/nssa/src/signature/public_key.rs @@ -21,3 +21,47 @@ impl PublicKey { } } +#[cfg(test)] +mod tests { + use crate::{PrivateKey, PublicKey}; + fn hex_to_32_bytes(hex: &str) -> [u8; 32] { + hex::decode(hex).unwrap().try_into().unwrap() + } + + /// Test vectors from + /// https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv + const BIP340_PK_TEST_VECTORS: [(&str, &str); 5] = [ + ( + "0000000000000000000000000000000000000000000000000000000000000003", + "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", + ), + ( + "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", + "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + ), + ( + "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", + "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", + ), + ( + "0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", + "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", + ), + ( + "0340034003400340034003400340034003400340034003400340034003400340", + "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117", + ), + ]; + + #[test] + fn test_bip340_pk_test_vectors() { + for (i, (private_key_hex, expected_public_key_hex)) in + BIP340_PK_TEST_VECTORS.iter().enumerate() + { + let key = PrivateKey::try_new(hex_to_32_bytes(private_key_hex)).unwrap(); + let public_key = PublicKey::new(&key); + let expected_public_key = PublicKey(hex_to_32_bytes(expected_public_key_hex)); + assert_eq!(public_key, expected_public_key, "Failed test vector at index {i}"); + } + } +}