implement public and private keys

This commit is contained in:
Sergio Chouhy 2025-08-11 19:14:12 -03:00
parent 54dd9aa814
commit 703a127f0e
8 changed files with 115 additions and 64 deletions

View File

@ -11,6 +11,7 @@ program-methods = { path = "program_methods" }
serde = "1.0.219"
serde_cbor = "0.11.2"
sha2 = "0.10.9"
secp256k1 = "0.31.1"
[dev-dependencies]
test-program-methods = { path = "test_program_methods" }

View File

@ -14,7 +14,7 @@ impl Address {
pub fn from_public_key(public_key: &PublicKey) -> Self {
// TODO: implement
Address::new([public_key.0; 32])
Address::new(public_key.0)
}
pub fn value(&self) -> &[u8; 32] {

View File

@ -13,4 +13,7 @@ pub enum NssaError {
#[error("Serialization error: {0}")]
InstructionSerializationError(String),
#[error("Invalid private key")]
InvalidPrivateKey,
}

View File

@ -21,8 +21,7 @@ impl Program {
pub fn serialize_instruction<T: Serialize>(
instruction: T,
) -> Result<InstructionData, NssaError> {
to_vec(&instruction)
.map_err(|e| NssaError::InstructionSerializationError(e.to_string()))
to_vec(&instruction).map_err(|e| NssaError::InstructionSerializationError(e.to_string()))
}
pub(crate) fn execute(

View File

@ -7,9 +7,39 @@ pub struct WitnessSet {
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
}
fn message_to_bytes(_message: &Message) -> Vec<u8> {
//TODO: implement
vec![0, 0]
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 {

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use crate::public_transaction::Message;
use crate::{error::NssaError, public_transaction::Message};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Signature(pub(crate) u8);
@ -9,32 +9,46 @@ pub struct Signature(pub(crate) u8);
// 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);
pub struct PrivateKey(pub(crate) [u8; 32]);
impl PrivateKey {
pub fn new(dummy_value: u8) -> Self {
Self(dummy_value)
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);
pub struct PublicKey(pub(crate) [u8; 32]);
impl PublicKey {
pub fn new(key: &PrivateKey) -> Self {
// TODO: implement
Self(key.0)
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(key.0)
Signature(0)
}
pub fn is_valid_for(&self, _message: &Message, public_key: &PublicKey) -> bool {
pub fn is_valid_for(&self, _message: &Message, _public_key: &PublicKey) -> bool {
// TODO: implement
self.0 == public_key.0
true
}
}

View File

@ -1,6 +1,6 @@
use crate::{
Address, PublicTransaction, V01State, error::NssaError, program::Program, public_transaction,
signature::PrivateKey,
Address, PublicKey, PublicTransaction, V01State, error::NssaError, program::Program,
public_transaction, signature::PrivateKey,
};
use nssa_core::account::Account;
@ -14,22 +14,24 @@ fn transfer_transaction(
let addresses = vec![from, to];
let nonces = vec![nonce];
let program_id = Program::authenticated_transfer_program().id();
let message = public_transaction::Message::try_new(program_id, addresses, nonces, balance).unwrap();
let message =
public_transaction::Message::try_new(program_id, addresses, nonces, balance).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
PublicTransaction::new(message, witness_set)
}
#[test]
fn transition_from_authenticated_transfer_program_invocation_default_account_destination() {
let initial_data = [([1; 32], 100)];
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from_public_key(&PublicKey::new(&key));
let initial_data = [(*address.value(), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = Address::new(initial_data[0].0);
let from_key = PrivateKey(1);
let from = address;
let to = Address::new([2; 32]);
assert_eq!(state.get_account_by_address(&to), Account::default());
let balance_to_move = 5;
let tx = transfer_transaction(from.clone(), from_key, 0, to.clone(), balance_to_move);
let tx = transfer_transaction(from.clone(), key, 0, to.clone(), balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&from).balance, 95);
@ -40,10 +42,12 @@ fn transition_from_authenticated_transfer_program_invocation_default_account_des
#[test]
fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() {
let initial_data = [([1; 32], 100)];
let key = PrivateKey::try_new([1; 32]).unwrap();
let address = Address::from_public_key(&PublicKey::new(&key));
let initial_data = [(*address.value(), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = Address::new(initial_data[0].0);
let from_key = PrivateKey(1);
let from = address;
let from_key = key;
let to = Address::new([2; 32]);
let balance_to_move = 101;
assert!(state.get_account_by_address(&from).balance < balance_to_move);
@ -60,11 +64,15 @@ fn transition_from_authenticated_transfer_program_invocation_insuficient_balance
#[test]
fn transition_from_authenticated_transfer_program_invocation_non_default_account_destination() {
let initial_data = [([1; 32], 100), ([99; 32], 200)];
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let address1 = Address::from_public_key(&PublicKey::new(&key1));
let address2 = Address::from_public_key(&PublicKey::new(&key2));
let initial_data = [(*address1.value(), 100), (*address2.value(), 200)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let from = Address::new(initial_data[1].0);
let from_key = PrivateKey(99);
let to = Address::new(initial_data[0].0);
let from = address2;
let from_key = key2;
let to = address1;
assert_ne!(state.get_account_by_address(&to), Account::default());
let balance_to_move = 8;
@ -79,37 +87,25 @@ fn transition_from_authenticated_transfer_program_invocation_non_default_account
#[test]
fn transition_from_chained_authenticated_transfer_program_invocations() {
let initial_data = [([1; 32], 100)];
let key1 = PrivateKey::try_new([1; 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));
let initial_data = [(*address1.value(), 100)];
let mut state = V01State::new_with_genesis_accounts(&initial_data);
let address_1 = Address::new(initial_data[0].0);
let key_1 = PrivateKey(1);
let address_2 = Address::new([2; 32]);
let key_2 = PrivateKey(2);
let address_3 = Address::new([3; 32]);
let address3 = Address::new([3; 32]);
let balance_to_move = 5;
let tx = transfer_transaction(
address_1.clone(),
key_1,
0,
address_2.clone(),
balance_to_move,
);
let tx = transfer_transaction(address1.clone(), key1, 0, address2.clone(), balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
let balance_to_move = 3;
let tx = transfer_transaction(
address_2.clone(),
key_2,
0,
address_3.clone(),
balance_to_move,
);
let tx = transfer_transaction(address2.clone(), key2, 0, address3.clone(), balance_to_move);
state.transition_from_public_transaction(&tx).unwrap();
assert_eq!(state.get_account_by_address(&address_1).balance, 95);
assert_eq!(state.get_account_by_address(&address_2).balance, 2);
assert_eq!(state.get_account_by_address(&address_3).balance, 3);
assert_eq!(state.get_account_by_address(&address_1).nonce, 1);
assert_eq!(state.get_account_by_address(&address_2).nonce, 1);
assert_eq!(state.get_account_by_address(&address_3).nonce, 0);
assert_eq!(state.get_account_by_address(&address1).balance, 95);
assert_eq!(state.get_account_by_address(&address2).balance, 2);
assert_eq!(state.get_account_by_address(&address3).balance, 3);
assert_eq!(state.get_account_by_address(&address1).nonce, 1);
assert_eq!(state.get_account_by_address(&address2).nonce, 1);
assert_eq!(state.get_account_by_address(&address3).nonce, 0);
}

View File

@ -61,7 +61,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_prog
assert_eq!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
@ -84,7 +85,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_bala
assert_eq!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
@ -107,7 +109,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_nonc
assert_ne!(account.nonce, Account::default().nonce);
assert_eq!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
@ -130,7 +133,8 @@ fn test_program_should_fail_if_modifies_program_owner_with_only_non_default_data
assert_eq!(account.nonce, Account::default().nonce);
assert_ne!(account.data, Account::default().data);
let program_id = Program::program_owner_changer().id();
let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
@ -156,7 +160,8 @@ fn test_program_should_fail_if_transfers_balance_from_non_owned_account() {
vec![sender_address, receiver_address],
vec![],
balance_to_move,
).unwrap();
)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
@ -178,7 +183,8 @@ fn test_program_should_fail_if_modifies_data_of_non_owned_account() {
state.get_account_by_address(&address).program_owner,
program_id
);
let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
@ -194,7 +200,8 @@ fn test_program_should_fail_if_does_not_preserve_total_balance_by_minting() {
let address = Address::new([1; 32]);
let program_id = Program::minter().id();
let message = public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], ()).unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
@ -219,7 +226,8 @@ fn test_program_should_fail_if_does_not_preserve_total_balance_by_burning() {
assert!(state.get_account_by_address(&address).balance > balance_to_burn);
let message =
public_transaction::Message::try_new(program_id, vec![address], vec![], balance_to_burn).unwrap();
public_transaction::Message::try_new(program_id, vec![address], vec![], balance_to_burn)
.unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
let tx = PublicTransaction::new(message, witness_set);
let result = state.transition_from_public_transaction(&tx);