fix: borsh derivation on publci transactions

This commit is contained in:
Pravdyvy 2025-11-18 17:52:46 +02:00
parent d69e8a292e
commit ef73336aa5
10 changed files with 20 additions and 146 deletions

View File

@ -12,6 +12,7 @@ chacha20 = { version = "0.9", default-features = false }
k256 = { version = "0.13.3", optional = true }
base58 = { version = "0.2.0", optional = true }
anyhow = { version = "1.0.98", optional = true }
borsh = "1.5.7"
[features]
default = []

View File

@ -1,3 +1,4 @@
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
#[cfg(feature = "host")]
@ -6,7 +7,7 @@ use std::{fmt::Display, str::FromStr};
#[cfg(feature = "host")]
use base58::{FromBase58, ToBase58};
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)]
#[cfg_attr(
any(feature = "host", test),
derive(Debug, Copy, PartialOrd, Ord, Default)

View File

@ -1574,6 +1574,7 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
name = "nssa-core"
version = "0.1.0"
dependencies = [
"borsh",
"chacha20",
"risc0-zkvm",
"serde",

View File

@ -1,153 +1,17 @@
// TODO: Consider switching to deriving Borsh
use std::io::{Cursor, Read};
use nssa_core::program::ProgramId;
use crate::{
Address, PublicKey, PublicTransaction, Signature,
error::NssaError,
public_transaction::{Message, WitnessSet},
};
const MESSAGE_ENCODING_PREFIX_LEN: usize = 32;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] =
b"/NSSA/v0.2/TxMessage/Public/\x00\x00\x00\x00";
use crate::{PublicTransaction, error::NssaError, public_transaction::Message};
impl Message {
/// Serializes a `Message` into bytes in the following layout:
/// PREFIX || <program_id> (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 to_bytes(&self) -> Vec<u8> {
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>
// 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 {
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
}
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 program_id: ProgramId = {
let mut this = [0u32; 8];
for item in &mut this {
*item = u32_from_cursor(cursor)?;
}
this
};
let addresses_len = u32_from_cursor(cursor)?;
let mut addresses = Vec::with_capacity(addresses_len as usize);
for _ in 0..addresses_len {
let mut value = [0u8; 32];
cursor.read_exact(&mut value)?;
addresses.push(Address::new(value))
}
let nonces_len = u32_from_cursor(cursor)?;
let mut nonces = Vec::with_capacity(nonces_len as usize);
for _ in 0..nonces_len {
let mut buf = [0u8; 16];
cursor.read_exact(&mut buf)?;
nonces.push(u128::from_le_bytes(buf))
}
let instruction_data_len = u32_from_cursor(cursor)?;
let mut instruction_data = Vec::with_capacity(instruction_data_len as usize);
for _ in 0..instruction_data_len {
let word = u32_from_cursor(cursor)?;
instruction_data.push(word)
}
Ok(Self {
program_id,
addresses,
nonces,
instruction_data,
})
}
}
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
}
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))
}
Ok(Self {
signatures_and_public_keys,
})
borsh::to_vec(&self).unwrap()
}
}
impl PublicTransaction {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.message().to_bytes();
bytes.extend_from_slice(&self.witness_set().to_bytes());
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)
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let message = Message::from_cursor(cursor)?;
let witness_set = WitnessSet::from_cursor(cursor)?;
Ok(PublicTransaction::new(message, witness_set))
Ok(borsh::from_slice(bytes)?)
}
}
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))
}

View File

@ -1,3 +1,4 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
account::Nonce,
program::{InstructionData, ProgramId},
@ -6,7 +7,7 @@ use serde::Serialize;
use crate::{Address, error::NssaError, program::Program};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Message {
pub(crate) program_id: ProgramId,
pub(crate) addresses: Vec<Address>,

View File

@ -1,5 +1,6 @@
use std::collections::{HashMap, HashSet};
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
account::{Account, AccountWithMetadata},
address::Address,
@ -13,7 +14,7 @@ use crate::{
public_transaction::{Message, WitnessSet},
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct PublicTransaction {
message: Message,
witness_set: WitnessSet,

View File

@ -1,6 +1,8 @@
use borsh::{BorshDeserialize, BorshSerialize};
use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct WitnessSet {
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
}

View File

@ -2,12 +2,13 @@ mod encoding;
mod private_key;
mod public_key;
use borsh::{BorshDeserialize, BorshSerialize};
pub use private_key::PrivateKey;
pub use public_key::PublicKey;
use rand::{RngCore, rngs::OsRng};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Signature {
value: [u8; 64],
}

View File

@ -1,10 +1,11 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::address::Address;
use crate::{PrivateKey, error::NssaError};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct PublicKey([u8; 32]);
impl PublicKey {

View File

@ -1579,6 +1579,7 @@ checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e"
name = "nssa-core"
version = "0.1.0"
dependencies = [
"borsh",
"chacha20",
"risc0-zkvm",
"serde",