add signatures

This commit is contained in:
Sergio Chouhy 2025-08-11 20:22:41 -03:00
parent 703a127f0e
commit 5bc1c9c688
10 changed files with 168 additions and 106 deletions

View File

@ -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)
}

View File

@ -29,4 +29,39 @@ impl Message {
instruction_data,
})
}
/// Serializes a `Message` into bytes in the following layout:
/// TAG || <program_id> (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<u8> {
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<u128>
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<u32>
// 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
}
}

View File

@ -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();

View File

@ -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 || <program_id> (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<u8> {
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<u128>
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<u32>
// 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<Item = &(Signature, PublicKey)> {
self.signatures_and_public_keys.iter()
}

View File

@ -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<Self, NssaError> {
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
}
}

View File

@ -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;

View File

@ -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<Self, NssaError> {
if Self::is_valid_key(value) {
Ok(Self(value))
} else {
Err(NssaError::InvalidPrivateKey)
}
}
}

View File

@ -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)
}
}

View File

@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// Serialize as a slice
serializer.serialize_bytes(&self.value)
}
}
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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 })
}
}

View File

@ -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));