diff --git a/nssa/src/address.rs b/nssa/src/address.rs index a8a9634..eeb9102 100644 --- a/nssa/src/address.rs +++ b/nssa/src/address.rs @@ -13,7 +13,7 @@ impl Address { } pub fn from_public_key(public_key: &PublicKey) -> Self { - // TODO: implement + // TODO: Check specs Address::new(public_key.0) } diff --git a/nssa/src/public_transaction/message.rs b/nssa/src/public_transaction/message.rs index 784777d..aaa04ed 100644 --- a/nssa/src/public_transaction/message.rs +++ b/nssa/src/public_transaction/message.rs @@ -29,4 +29,39 @@ impl Message { instruction_data, }) } + + /// 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) + /// 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 { + const MESSAGE_ENCODING_PREFIX: &[u8; 19] = b"NSSA/v0.1/TxMessage"; + + let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec(); + // program_id: [u32; 8] + for word in &self.program_id { + bytes.extend_from_slice(&word.to_le_bytes()); + } + // addresses: Vec<[u8;32]> + // serialize length as u32 little endian, then all addresses concatenated + let addresses_len = self.addresses.len() as u32; + bytes.extend(&addresses_len.to_le_bytes()); + for addr in &self.addresses { + bytes.extend_from_slice(addr.value()); + } + // nonces: Vec + let nonces_len = self.nonces.len() as u32; + bytes.extend(&nonces_len.to_le_bytes()); + for nonce in &self.nonces { + bytes.extend(&nonce.to_le_bytes()); + } + // instruction_data: Vec + // serialize length as u32 little endian, then all addresses concatenated + let instr_len = self.instruction_data.len() as u32; + bytes.extend(&instr_len.to_le_bytes()); + for word in &self.instruction_data { + bytes.extend(&word.to_le_bytes()); + } + + bytes + } } diff --git a/nssa/src/public_transaction/mod.rs b/nssa/src/public_transaction/mod.rs index 64e051b..162257a 100644 --- a/nssa/src/public_transaction/mod.rs +++ b/nssa/src/public_transaction/mod.rs @@ -72,23 +72,20 @@ impl PublicTransaction { )); } - let mut authorized_addresses = Vec::new(); - for ((signature, public_key), nonce) in witness_set.iter_signatures().zip(&message.nonces) { - // Check the signature is valid - if !signature.is_valid_for(message, public_key) { - return Err(NssaError::InvalidInput( - "Invalid signature for given message and public key".into(), - )); - } + // Check the signatures are valid + if !witness_set.is_valid_for(message) { + return Err(NssaError::InvalidInput( + "Invalid signature for given message and public key".into(), + )); + } - // Check the nonce corresponds to the current nonce on the public state. - let address = Address::from_public_key(public_key); - let current_nonce = state.get_account_by_address(&address).nonce; + let signer_addresses = self.signer_addresses(); + // Check nonces corresponds to the current nonces on the public state. + for (address, nonce) in signer_addresses.iter().zip(&message.nonces) { + let current_nonce = state.get_account_by_address(address).nonce; if current_nonce != *nonce { return Err(NssaError::InvalidInput("Nonce mismatch".into())); } - - authorized_addresses.push(address); } // Build pre_states for execution @@ -97,7 +94,7 @@ impl PublicTransaction { .iter() .map(|address| AccountWithMetadata { account: state.get_account_by_address(address), - is_authorized: authorized_addresses.contains(address), + is_authorized: signer_addresses.contains(address), }) .collect(); diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index 61e1683..05507e7 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -7,44 +7,9 @@ pub struct WitnessSet { pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>, } -const MESSAGE_ENCODING_PREFIX: &[u8; 19] = b"NSSA/v0.1/TxMessage"; - -/// 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) -/// Integers and words are encoded in little-endian byte order, and fields appear in the above order. -fn message_to_bytes(message: &Message) -> Vec { - let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec(); - // program_id: [u32; 8] - for word in &message.program_id { - bytes.extend_from_slice(&word.to_le_bytes()); - } - // addresses: Vec<[u8;32]> - // serialize length as u32 little endian, then all addresses concatenated - let addresses_len = message.addresses.len() as u32; - bytes.extend(&addresses_len.to_le_bytes()); - for addr in &message.addresses { - bytes.extend_from_slice(addr.value()); - } - // nonces: Vec - let nonces_len = message.nonces.len() as u32; - bytes.extend(&nonces_len.to_le_bytes()); - for nonce in &message.nonces { - bytes.extend(&nonce.to_le_bytes()); - } - // instruction_data: Vec - // serialize length as u32 little endian, then all addresses concatenated - let instr_len = message.instruction_data.len() as u32; - bytes.extend(&instr_len.to_le_bytes()); - for word in &message.instruction_data { - bytes.extend(&word.to_le_bytes()); - } - - bytes -} - impl WitnessSet { pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self { - let message_bytes = message_to_bytes(message); + let message_bytes = message.message_to_bytes(); let signatures_and_public_keys = private_keys .iter() .map(|&key| (Signature::new(key, &message_bytes), PublicKey::new(key))) @@ -54,6 +19,16 @@ impl WitnessSet { } } + pub(crate) fn is_valid_for(&self, message: &Message) -> bool { + let message_bytes = message.message_to_bytes(); + for (signature, public_key) in self.iter_signatures() { + if !signature.is_valid_for(&message_bytes, &public_key) { + return false; + } + } + true + } + pub fn iter_signatures(&self) -> impl Iterator { self.signatures_and_public_keys.iter() } diff --git a/nssa/src/signature.rs b/nssa/src/signature.rs deleted file mode 100644 index 02fe7b3..0000000 --- a/nssa/src/signature.rs +++ /dev/null @@ -1,54 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{error::NssaError, public_transaction::Message}; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct Signature(pub(crate) u8); - -// TODO: Dummy impl. Replace by actual private key. -// TODO: Remove Debug, Clone, Serialize, Deserialize, PartialEq and Eq for security reasons -// TODO: Implement Zeroize -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct PrivateKey(pub(crate) [u8; 32]); - -impl PrivateKey { - fn is_valid_key(value: [u8; 32]) -> bool { - secp256k1::SecretKey::from_byte_array(value).is_ok() - } - - pub fn try_new(value: [u8; 32]) -> Result { - if Self::is_valid_key(value) { - Ok(Self(value)) - } else { - Err(NssaError::InvalidPrivateKey) - } - } -} - -// TODO: Dummy impl. Replace by actual public key. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct PublicKey(pub(crate) [u8; 32]); - -impl PublicKey { - pub fn new(key: &PrivateKey) -> Self { - let value = { - let secret_key = secp256k1::SecretKey::from_byte_array(key.0).unwrap(); - let public_key = - secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key); - let (x_only, _) = public_key.x_only_public_key(); - x_only.serialize() - }; - Self(value) - } -} - -impl Signature { - pub(crate) fn new(key: &PrivateKey, _message: &[u8]) -> Self { - Signature(0) - } - - pub fn is_valid_for(&self, _message: &Message, _public_key: &PublicKey) -> bool { - // TODO: implement - true - } -} diff --git a/nssa/src/signature/mod.rs b/nssa/src/signature/mod.rs new file mode 100644 index 0000000..9539e45 --- /dev/null +++ b/nssa/src/signature/mod.rs @@ -0,0 +1,7 @@ +mod private_key; +mod public_key; +mod signature; + +pub use private_key::PrivateKey; +pub use public_key::PublicKey; +pub use signature::Signature; diff --git a/nssa/src/signature/private_key.rs b/nssa/src/signature/private_key.rs new file mode 100644 index 0000000..78a2ad5 --- /dev/null +++ b/nssa/src/signature/private_key.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +use crate::error::NssaError; + + +// TODO: Dummy impl. Replace by actual private key. +// TODO: Remove Debug, Clone, Serialize, Deserialize, PartialEq and Eq for security reasons +// TODO: Implement Zeroize +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PrivateKey(pub(crate) [u8; 32]); + +impl PrivateKey { + fn is_valid_key(value: [u8; 32]) -> bool { + secp256k1::SecretKey::from_byte_array(value).is_ok() + } + + pub fn try_new(value: [u8; 32]) -> Result { + if Self::is_valid_key(value) { + Ok(Self(value)) + } else { + Err(NssaError::InvalidPrivateKey) + } + } +} + diff --git a/nssa/src/signature/public_key.rs b/nssa/src/signature/public_key.rs new file mode 100644 index 0000000..1073cba --- /dev/null +++ b/nssa/src/signature/public_key.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use crate::PrivateKey; + + +// TODO: Dummy impl. Replace by actual public key. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct PublicKey(pub(crate) [u8; 32]); + +impl PublicKey { + pub fn new(key: &PrivateKey) -> Self { + let value = { + let secret_key = secp256k1::SecretKey::from_byte_array(key.0).unwrap(); + let public_key = + secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key); + let (x_only, _) = public_key.x_only_public_key(); + x_only.serialize() + }; + Self(value) + } +} diff --git a/nssa/src/signature/signature.rs b/nssa/src/signature/signature.rs new file mode 100644 index 0000000..eb0fe24 --- /dev/null +++ b/nssa/src/signature/signature.rs @@ -0,0 +1,56 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{PrivateKey, PublicKey, error::NssaError, public_transaction::Message}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Signature { + value: [u8; 64], +} + +impl Signature { + pub(crate) fn new(key: &PrivateKey, message: &[u8]) -> Self { + let value = { + let secp = secp256k1::Secp256k1::new(); + let secret_key = secp256k1::SecretKey::from_byte_array(key.0).unwrap(); + let keypair = secp256k1::Keypair::from_secret_key(&secp, &secret_key); + let signature = secp.sign_schnorr_no_aux_rand(message, &keypair); + signature.to_byte_array() + }; + Self { value } + } + + pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool { + let pk = secp256k1::XOnlyPublicKey::from_byte_array(public_key.0).unwrap(); + let secp = secp256k1::Secp256k1::new(); + let sig = secp256k1::schnorr::Signature::from_byte_array(self.value); + secp.verify_schnorr(&sig, &bytes, &pk).is_ok() + } +} + +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Serialize as a slice + serializer.serialize_bytes(&self.value) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes: &[u8] = Deserialize::deserialize(deserializer)?; + if bytes.len() != 64 { + return Err(serde::de::Error::invalid_length( + bytes.len(), + &"expected 64 bytes", + )); + } + let mut value = [0u8; 64]; + value.copy_from_slice(bytes); + Ok(Signature { value }) + } +} diff --git a/nssa/src/tests/state_tests.rs b/nssa/src/tests/state_tests.rs index c0745f7..5acbed8 100644 --- a/nssa/src/tests/state_tests.rs +++ b/nssa/src/tests/state_tests.rs @@ -87,7 +87,7 @@ fn transition_from_authenticated_transfer_program_invocation_non_default_account #[test] fn transition_from_chained_authenticated_transfer_program_invocations() { - let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let key1 = PrivateKey::try_new([8; 32]).unwrap(); let address1 = Address::from_public_key(&PublicKey::new(&key1)); let key2 = PrivateKey::try_new([2; 32]).unwrap(); let address2 = Address::from_public_key(&PublicKey::new(&key2));