fix: borsh integrated into transactions

This commit is contained in:
Pravdyvy 2025-11-19 14:28:18 +02:00
parent 30bcd20ac5
commit 2f07853975
9 changed files with 21 additions and 342 deletions

View File

@ -6,7 +6,9 @@ pub type Nonce = u128;
pub type Data = Vec<u8>;
/// Account to be used both in public and private contexts
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
#[derive(
Serialize, Deserialize, Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize,
)]
#[cfg_attr(any(feature = "host", test), derive(Debug))]
pub struct Account {
pub program_owner: ProgramId,

View File

@ -1,209 +1,15 @@
use std::io::{Cursor, Read};
use nssa_core::{
Commitment, Nullifier,
account::Account,
encryption::{Ciphertext, EphemeralPublicKey},
};
use crate::{
Address, PrivacyPreservingTransaction, PublicKey, Signature,
error::NssaError,
privacy_preserving_transaction::{
circuit::Proof,
message::{EncryptedAccountData, Message},
witness_set::WitnessSet,
},
PrivacyPreservingTransaction, error::NssaError,
privacy_preserving_transaction::message::Message,
};
const MESSAGE_ENCODING_PREFIX_LEN: usize = 32;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] =
b"/NSSA/v0.2/TxMessage/Private/\x00\x00\x00";
impl EncryptedAccountData {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.ciphertext.to_bytes();
bytes.extend_from_slice(&self.epk.to_bytes());
bytes.push(self.view_tag);
bytes
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let ciphertext = Ciphertext::from_cursor(cursor)?;
let epk = EphemeralPublicKey::from_cursor(cursor)?;
let mut tag_bytes = [0; 1];
cursor.read_exact(&mut tag_bytes)?;
let view_tag = tag_bytes[0];
Ok(Self {
ciphertext,
epk,
view_tag,
})
}
}
impl Message {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec();
// Public addresses
let public_addresses_len: u32 = self.public_addresses.len() as u32;
bytes.extend_from_slice(&public_addresses_len.to_le_bytes());
for address in &self.public_addresses {
bytes.extend_from_slice(address.value());
}
// Nonces
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());
}
// Public post states
let public_post_states_len: u32 = self.public_post_states.len() as u32;
bytes.extend_from_slice(&public_post_states_len.to_le_bytes());
for account in &self.public_post_states {
bytes.extend_from_slice(&account.to_bytes());
}
// Encrypted post states
let encrypted_accounts_post_states_len: u32 =
self.encrypted_private_post_states.len() as u32;
bytes.extend_from_slice(&encrypted_accounts_post_states_len.to_le_bytes());
for encrypted_account in &self.encrypted_private_post_states {
bytes.extend_from_slice(&encrypted_account.to_bytes());
}
// New commitments
let new_commitments_len: u32 = self.new_commitments.len() as u32;
bytes.extend_from_slice(&new_commitments_len.to_le_bytes());
for commitment in &self.new_commitments {
bytes.extend_from_slice(&commitment.to_byte_array());
}
// New nullifiers
let new_nullifiers_len: u32 = self.new_nullifiers.len() as u32;
bytes.extend_from_slice(&new_nullifiers_len.to_le_bytes());
for (nullifier, commitment_set_digest) in &self.new_nullifiers {
bytes.extend_from_slice(&nullifier.to_byte_array());
bytes.extend_from_slice(commitment_set_digest);
}
bytes
pub fn to_bytes(&self) -> Vec<u8> {
borsh::to_vec(&self).unwrap()
}
#[allow(unused)]
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let prefix = {
let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN];
cursor.read_exact(&mut this)?;
this
};
if &prefix != MESSAGE_ENCODING_PREFIX {
return Err(NssaError::TransactionDeserializationError(
"Invalid privacy preserving message prefix".to_string(),
));
}
let mut len_bytes = [0u8; 4];
// Public addresses
cursor.read_exact(&mut len_bytes)?;
let public_addresses_len = u32::from_le_bytes(len_bytes) as usize;
let mut public_addresses = Vec::with_capacity(public_addresses_len);
for _ in 0..public_addresses_len {
let mut value = [0u8; 32];
cursor.read_exact(&mut value)?;
public_addresses.push(Address::new(value))
}
// Nonces
cursor.read_exact(&mut len_bytes)?;
let nonces_len = u32::from_le_bytes(len_bytes) as usize;
let mut nonces = Vec::with_capacity(nonces_len);
for _ in 0..nonces_len {
let mut buf = [0u8; 16];
cursor.read_exact(&mut buf)?;
nonces.push(u128::from_le_bytes(buf))
}
// Public post states
cursor.read_exact(&mut len_bytes)?;
let public_post_states_len = u32::from_le_bytes(len_bytes) as usize;
let mut public_post_states = Vec::with_capacity(public_post_states_len);
for _ in 0..public_post_states_len {
public_post_states.push(Account::from_cursor(cursor)?);
}
// Encrypted private post states
cursor.read_exact(&mut len_bytes)?;
let encrypted_len = u32::from_le_bytes(len_bytes) as usize;
let mut encrypted_private_post_states = Vec::with_capacity(encrypted_len);
for _ in 0..encrypted_len {
encrypted_private_post_states.push(EncryptedAccountData::from_cursor(cursor)?);
}
// New commitments
cursor.read_exact(&mut len_bytes)?;
let new_commitments_len = u32::from_le_bytes(len_bytes) as usize;
let mut new_commitments = Vec::with_capacity(new_commitments_len);
for _ in 0..new_commitments_len {
new_commitments.push(Commitment::from_cursor(cursor)?);
}
// New nullifiers
cursor.read_exact(&mut len_bytes)?;
let new_nullifiers_len = u32::from_le_bytes(len_bytes) as usize;
let mut new_nullifiers = Vec::with_capacity(new_nullifiers_len);
for _ in 0..new_nullifiers_len {
let nullifier = Nullifier::from_cursor(cursor)?;
let mut commitment_set_digest = [0; 32];
cursor.read_exact(&mut commitment_set_digest)?;
new_nullifiers.push((nullifier, commitment_set_digest));
}
Ok(Self {
public_addresses,
nonces,
public_post_states,
encrypted_private_post_states,
new_commitments,
new_nullifiers,
})
}
}
impl WitnessSet {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
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.to_bytes());
bytes.extend_from_slice(public_key.to_bytes());
}
bytes.extend_from_slice(&self.proof.to_bytes());
bytes
}
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let num_signatures: u32 = {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf)?;
u32::from_le_bytes(buf)
};
let mut signatures_and_public_keys = Vec::with_capacity(num_signatures as usize);
for _i in 0..num_signatures {
let signature = Signature::from_cursor(cursor)?;
let public_key = PublicKey::from_cursor(cursor)?;
signatures_and_public_keys.push((signature, public_key))
}
let proof = Proof::from_cursor(cursor)?;
Ok(Self {
signatures_and_public_keys,
proof,
})
pub fn from_bytes(bytes: &[u8]) -> Result<Self, NssaError> {
Ok(borsh::from_slice(bytes)?)
}
}
@ -216,34 +22,3 @@ impl PrivacyPreservingTransaction {
Ok(borsh::from_slice(bytes)?)
}
}
impl Proof {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let proof_len = self.0.len() as u32;
bytes.extend_from_slice(&proof_len.to_le_bytes());
bytes.extend_from_slice(&self.0);
bytes
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let proof_len = u32_from_cursor(cursor) as usize;
let mut proof = Vec::with_capacity(proof_len);
for _ in 0..proof_len {
let mut one_byte_buf = [0u8];
cursor.read_exact(&mut one_byte_buf)?;
proof.push(one_byte_buf[0]);
}
Ok(Self(proof))
}
}
// TODO: Improve error handling. Remove unwraps.
pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 {
let mut word_buf = [0u8; 4];
cursor.read_exact(&mut word_buf).unwrap();
u32::from_le_bytes(word_buf)
}

View File

@ -1,77 +1,17 @@
// TODO: Consider switching to deriving Borsh
use std::io::{Cursor, Read};
use crate::{
ProgramDeploymentTransaction, error::NssaError, program_deployment_transaction::Message,
};
const MESSAGE_ENCODING_PREFIX_LEN: usize = 32;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] =
b"/NSSA/v0.2/TxMessage/Program/\x00\x00\x00";
impl Message {
/// Serializes a `Message` into bytes in the following layout:
/// PREFIX || bytecode_len (4 bytes LE) || <bytecode>
/// Integers are encoded in little-endian byte order, and fields appear in the above order.
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = MESSAGE_ENCODING_PREFIX.to_vec();
let bytecode_len = self.bytecode.len() as u32;
bytes.extend(&bytecode_len.to_le_bytes());
bytes.extend(&self.bytecode);
bytes
}
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let prefix = {
let mut this = [0u8; MESSAGE_ENCODING_PREFIX_LEN];
cursor.read_exact(&mut this)?;
this
};
if &prefix != MESSAGE_ENCODING_PREFIX {
return Err(NssaError::TransactionDeserializationError(
"Invalid public message prefix".to_string(),
));
}
let bytecode_len = u32_from_cursor(cursor)?;
let mut bytecode = vec![0; bytecode_len as usize];
let num_bytes = cursor.read(&mut bytecode)?;
if num_bytes != bytecode_len as usize {
println!("num bytes: {}", num_bytes);
return Err(NssaError::TransactionDeserializationError(
"Invalid number of bytes".to_string(),
));
}
Ok(Self { bytecode })
}
}
use crate::{ProgramDeploymentTransaction, error::NssaError};
impl ProgramDeploymentTransaction {
pub fn to_bytes(&self) -> Vec<u8> {
self.message.to_bytes()
borsh::to_vec(&self).unwrap()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, NssaError> {
let mut cursor = Cursor::new(bytes);
Self::from_cursor(&mut cursor)
Ok(borsh::from_slice(bytes)?)
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let message = Message::from_cursor(cursor)?;
Ok(Self::new(message))
}
}
fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<u32, NssaError> {
let mut word_buf = [0u8; 4];
cursor.read_exact(&mut word_buf)?;
Ok(u32::from_le_bytes(word_buf))
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use crate::{ProgramDeploymentTransaction, program_deployment_transaction::Message};
#[test]
@ -79,8 +19,7 @@ mod tests {
let message = Message::new(vec![0xca, 0xfe, 0xca, 0xfe, 0x01, 0x02, 0x03]);
let tx = ProgramDeploymentTransaction::new(message);
let bytes = tx.to_bytes();
let mut cursor = Cursor::new(bytes.as_ref());
let tx_from_cursor = ProgramDeploymentTransaction::from_cursor(&mut cursor).unwrap();
assert_eq!(tx, tx_from_cursor);
let tx_from_bytes = ProgramDeploymentTransaction::from_bytes(&bytes).unwrap();
assert_eq!(tx, tx_from_bytes);
}
}

View File

@ -91,8 +91,6 @@ impl Message {
#[cfg(test)]
pub mod tests {
use std::io::Cursor;
use nssa_core::{
Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, SharedSecretKey,
account::Account,
@ -141,17 +139,6 @@ pub mod tests {
}
}
#[test]
fn test_message_serialization_roundtrip() {
let message = message_for_tests();
let bytes = message.to_bytes();
let mut cursor = Cursor::new(bytes.as_ref());
let message_from_cursor = Message::from_cursor(&mut cursor).unwrap();
assert_eq!(message, message_from_cursor);
}
#[test]
fn test_encrypted_account_data_constructor() {
let npk = NullifierPublicKey::from(&[1; 32]);

View File

@ -1,4 +1,6 @@
#[derive(Debug, Clone, PartialEq, Eq)]
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Message {
pub(crate) bytecode: Vec<u8>,
}

View File

@ -1,8 +1,10 @@
use borsh::{BorshDeserialize, BorshSerialize};
use crate::{
V02State, error::NssaError, program::Program, program_deployment_transaction::message::Message,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct ProgramDeploymentTransaction {
pub(crate) message: Message,
}

View File

@ -1,27 +0,0 @@
use std::io::{Cursor, Read};
use crate::{PublicKey, Signature, error::NssaError};
impl PublicKey {
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let mut value = [0u8; 32];
cursor.read_exact(&mut value)?;
Self::try_new(value)
}
pub(crate) fn to_bytes(&self) -> &[u8] {
self.value()
}
}
impl Signature {
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let mut value = [0u8; 64];
cursor.read_exact(&mut value)?;
Ok(Self { value })
}
pub(crate) fn to_bytes(&self) -> &[u8] {
&self.value
}
}

View File

@ -1,4 +1,3 @@
mod encoding;
mod private_key;
mod public_key;

View File

@ -20,7 +20,7 @@ impl PublicKey {
Self(value)
}
pub(super) fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
// Check point is valid
let _ = secp256k1::XOnlyPublicKey::from_byte_array(value)
.map_err(|_| NssaError::InvalidPublicKey)?;