feat: introduce account data size limit

This commit is contained in:
Daniil Polyakov 2025-12-05 02:17:09 +03:00
parent f6e2bcaad7
commit 4574acfc49
17 changed files with 312 additions and 89 deletions

View File

@ -356,8 +356,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!( assert_eq!(
definition_acc.data, definition_acc.data.as_ref(),
vec![ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
); );
@ -375,11 +375,14 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16
// bytes) ] First byte of the data equal to 1 means it's a token holding account // bytes) ] First byte of the data equal to 1 means it's a token holding account
assert_eq!(supply_acc.data[0], 1); assert_eq!(supply_acc.data.as_ref()[0], 1);
// Bytes from 1 to 33 represent the id of the token this account is associated with. // Bytes from 1 to 33 represent the id of the token this account is associated with.
// In this example, this is a token account of the newly created token, so it is expected // In this example, this is a token account of the newly created token, so it is expected
// to be equal to the account_id of the token definition account. // to be equal to the account_id of the token definition account.
assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); assert_eq!(
&supply_acc.data.as_ref()[1..33],
definition_account_id.to_bytes()
);
assert_eq!( assert_eq!(
u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()),
37 37
@ -518,8 +521,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!( assert_eq!(
definition_acc.data, definition_acc.data.as_ref(),
vec![ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
); );
@ -679,8 +682,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!( assert_eq!(
definition_acc.data, definition_acc.data.as_ref(),
vec![ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
); );
@ -821,8 +824,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!( assert_eq!(
definition_acc.data, definition_acc.data.as_ref(),
vec![ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
); );
@ -963,8 +966,8 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
// The data of a token definition account has the following layout: // The data of a token definition account has the following layout:
// [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ]
assert_eq!( assert_eq!(
definition_acc.data, definition_acc.data.as_ref(),
vec![ &[
0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
); );
@ -1480,7 +1483,7 @@ pub fn prepare_function_map() -> HashMap<String, TestFunction> {
.account; .account;
assert_eq!(post_state_account.program_owner, data_changer.id()); assert_eq!(post_state_account.program_owner, data_changer.id());
assert_eq!(post_state_account.balance, 0); assert_eq!(post_state_account.balance, 0);
assert_eq!(post_state_account.data, vec![0]); assert_eq!(post_state_account.data.as_ref(), &[0]);
assert_eq!(post_state_account.nonce, 0); assert_eq!(post_state_account.nonce, 0);
info!("Success!"); info!("Success!");

View File

@ -8,7 +8,8 @@ use nssa::{
public_transaction as putx, public_transaction as putx,
}; };
use nssa_core::{ use nssa_core::{
MembershipProof, NullifierPublicKey, account::AccountWithMetadata, MembershipProof, NullifierPublicKey,
account::{AccountWithMetadata, data::Data},
encryption::IncomingViewingPublicKey, encryption::IncomingViewingPublicKey,
}; };
use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig}; use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig};
@ -90,7 +91,7 @@ impl TpsTestManager {
balance: 100, balance: 100,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
program_owner: Program::authenticated_transfer_program().id(), program_owner: Program::authenticated_transfer_program().id(),
data: vec![], data: Data::default(),
}; };
let initial_commitment = CommitmentsInitialData { let initial_commitment = CommitmentsInitialData {
npk: sender_npk, npk: sender_npk,
@ -129,7 +130,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
balance: 100, balance: 100,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
program_owner: program.id(), program_owner: program.id(),
data: vec![], data: Data::default(),
}, },
true, true,
AccountId::from(&sender_npk), AccountId::from(&sender_npk),

View File

@ -6,7 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
risc0-zkvm = { version = "3.0.3", features = ['std'] } risc0-zkvm = { version = "3.0.3", features = ['std'] }
serde = { version = "1.0", default-features = false } serde = { version = "1.0", default-features = false }
thiserror = { version = "2.0.12", optional = true } thiserror = { version = "2.0.12" }
bytemuck = { version = "1.13", optional = true } bytemuck = { version = "1.13", optional = true }
chacha20 = { version = "0.9", default-features = false } chacha20 = { version = "0.9", default-features = false }
k256 = { version = "0.13.3", optional = true } k256 = { version = "0.13.3", optional = true }
@ -14,6 +14,9 @@ base58 = { version = "0.2.0", optional = true }
anyhow = { version = "1.0.98", optional = true } anyhow = { version = "1.0.98", optional = true }
borsh = "1.5.7" borsh = "1.5.7"
[dev-dependencies]
serde_json.workspace = true
[features] [features]
default = [] default = []
host = ["thiserror", "bytemuck", "k256", "base58", "anyhow"] host = ["bytemuck", "k256", "base58", "anyhow"]

View File

@ -4,12 +4,14 @@ use std::{fmt::Display, str::FromStr};
#[cfg(feature = "host")] #[cfg(feature = "host")]
use base58::{FromBase58, ToBase58}; use base58::{FromBase58, ToBase58};
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
pub use data::Data;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::program::ProgramId; use crate::program::ProgramId;
pub mod data;
pub type Nonce = u128; pub type Nonce = u128;
pub type Data = Vec<u8>;
/// Account to be used both in public and private contexts /// Account to be used both in public and private contexts
#[derive( #[derive(
@ -139,7 +141,10 @@ mod tests {
let account = Account { let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337, balance: 1337,
data: b"testing_account_with_metadata_constructor".to_vec(), data: b"testing_account_with_metadata_constructor"
.to_vec()
.try_into()
.unwrap(),
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
}; };
let fingerprint = AccountId::new([8; 32]); let fingerprint = AccountId::new([8; 32]);

View File

@ -0,0 +1,175 @@
use std::ops::Deref;
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
#[cfg(feature = "host")]
use crate::error::NssaCoreError;
pub const DATA_MAX_LENGTH_IN_BYTES: usize = 100 * 1024; // 100 KiB
#[derive(Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)]
#[cfg_attr(any(feature = "host", test), derive(Debug))]
pub struct Data(Vec<u8>);
impl Data {
pub fn into_inner(self) -> Vec<u8> {
self.0
}
#[cfg(feature = "host")]
pub fn from_cursor(cursor: &mut std::io::Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
use std::io::Read as _;
let mut u32_bytes = [0u8; 4];
cursor.read_exact(&mut u32_bytes)?;
let data_length = u32::from_le_bytes(u32_bytes);
if data_length as usize > DATA_MAX_LENGTH_IN_BYTES {
return Err(
std::io::Error::new(std::io::ErrorKind::InvalidData, DataTooBigError).into(),
);
}
let mut data = vec![0; data_length as usize];
cursor.read_exact(&mut data)?;
Ok(Self(data))
}
}
#[derive(Debug, thiserror::Error)]
#[error("data length exceeds maximum allowed length of {DATA_MAX_LENGTH_IN_BYTES} bytes")]
pub struct DataTooBigError;
impl From<Data> for Vec<u8> {
fn from(data: Data) -> Self {
data.0
}
}
impl TryFrom<Vec<u8>> for Data {
type Error = DataTooBigError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
if value.len() > DATA_MAX_LENGTH_IN_BYTES {
Err(DataTooBigError)
} else {
Ok(Self(value))
}
}
}
impl Deref for Data {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<[u8]> for Data {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl<'de> Deserialize<'de> for Data {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
/// Data deserialization visitor.
///
/// Compared to a simple deserialization into a `Vec<u8>`, this visitor enforces
/// early length check defined by [`DATA_MAX_LENGTH_IN_BYTES`].
struct DataVisitor;
impl<'de> serde::de::Visitor<'de> for DataVisitor {
type Value = Data;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
formatter,
"a byte array with length not exceeding {} bytes",
DATA_MAX_LENGTH_IN_BYTES
)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut vec =
Vec::with_capacity(seq.size_hint().unwrap_or(0).min(DATA_MAX_LENGTH_IN_BYTES));
while let Some(value) = seq.next_element()? {
if vec.len() >= DATA_MAX_LENGTH_IN_BYTES {
return Err(serde::de::Error::custom(DataTooBigError));
}
vec.push(value);
}
Ok(Data(vec))
}
}
deserializer.deserialize_seq(DataVisitor)
}
}
impl BorshDeserialize for Data {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
// Implementation adapted from `impl BorshDeserialize for Vec<T>`
let len = u32::deserialize_reader(reader)?;
match len {
0 => Ok(Self::default()),
len if len as usize > DATA_MAX_LENGTH_IN_BYTES => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
DataTooBigError,
)),
len => {
let vec_bytes = u8::vec_from_reader(len, reader)?
.expect("can't be None in current borsh crate implementation");
Ok(Self(vec_bytes))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_data_max_length_allowed() {
let max_vec = vec![0u8; DATA_MAX_LENGTH_IN_BYTES];
let result = Data::try_from(max_vec);
assert!(result.is_ok());
}
#[test]
fn test_data_too_big_error() {
let big_vec = vec![0u8; DATA_MAX_LENGTH_IN_BYTES + 1];
let result = Data::try_from(big_vec);
assert!(matches!(result, Err(DataTooBigError)));
}
#[test]
fn test_borsh_deserialize_exceeding_limit_error() {
let too_big_data = vec![0u8; DATA_MAX_LENGTH_IN_BYTES + 1];
let mut serialized = Vec::new();
<_ as BorshSerialize>::serialize(&too_big_data, &mut serialized).unwrap();
let result = <Data as BorshDeserialize>::deserialize(&mut serialized.as_ref());
assert!(result.is_err());
}
#[test]
fn test_json_deserialize_exceeding_limit_error() {
let data = vec![0u8; DATA_MAX_LENGTH_IN_BYTES + 1];
let json = serde_json::to_string(&data).unwrap();
let result: Result<Data, _> = serde_json::from_str(&json);
assert!(result.is_err());
}
}

View File

@ -54,7 +54,7 @@ mod tests {
Account { Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 12345678901234567890, balance: 12345678901234567890,
data: b"test data".to_vec(), data: b"test data".to_vec().try_into().unwrap(),
nonce: 18446744073709551614, nonce: 18446744073709551614,
}, },
true, true,
@ -64,7 +64,7 @@ mod tests {
Account { Account {
program_owner: [9, 9, 9, 8, 8, 8, 7, 7], program_owner: [9, 9, 9, 8, 8, 8, 7, 7],
balance: 123123123456456567112, balance: 123123123456456567112,
data: b"test data".to_vec(), data: b"test data".to_vec().try_into().unwrap(),
nonce: 9999999999999999999999, nonce: 9999999999999999999999,
}, },
false, false,
@ -74,7 +74,7 @@ mod tests {
public_post_states: vec![Account { public_post_states: vec![Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 100, balance: 100,
data: b"post state data".to_vec(), data: b"post state data".to_vec().try_into().unwrap(),
nonce: 18446744073709551615, nonce: 18446744073709551615,
}], }],
ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])], ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])],

View File

@ -26,12 +26,14 @@ impl Account {
bytes.extend_from_slice(&self.nonce.to_le_bytes()); bytes.extend_from_slice(&self.nonce.to_le_bytes());
let data_length: u32 = self.data.len() as u32; let data_length: u32 = self.data.len() as u32;
bytes.extend_from_slice(&data_length.to_le_bytes()); bytes.extend_from_slice(&data_length.to_le_bytes());
bytes.extend_from_slice(self.data.as_slice()); bytes.extend_from_slice(self.data.as_ref());
bytes bytes
} }
#[cfg(feature = "host")] #[cfg(feature = "host")]
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> { pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaCoreError> {
use crate::account::data::Data;
let mut u32_bytes = [0u8; 4]; let mut u32_bytes = [0u8; 4];
let mut u128_bytes = [0u8; 16]; let mut u128_bytes = [0u8; 16];
@ -51,10 +53,7 @@ impl Account {
let nonce = u128::from_le_bytes(u128_bytes); let nonce = u128::from_le_bytes(u128_bytes);
// data // data
cursor.read_exact(&mut u32_bytes)?; let data = Data::from_cursor(cursor)?;
let data_length = u32::from_le_bytes(u32_bytes);
let mut data = vec![0; data_length as usize];
cursor.read_exact(&mut data)?;
Ok(Self { Ok(Self {
program_owner, program_owner,
@ -149,7 +148,7 @@ mod tests {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 123456789012345678901234567890123456, balance: 123456789012345678901234567890123456,
nonce: 42, nonce: 42,
data: b"hola mundo".to_vec(), data: b"hola mundo".to_vec().try_into().unwrap(),
}; };
// program owner || balance || nonce || data_len || data // program owner || balance || nonce || data_len || data
@ -210,7 +209,7 @@ mod tests {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 123456789012345678901234567890123456, balance: 123456789012345678901234567890123456,
nonce: 42, nonce: 42,
data: b"hola mundo".to_vec(), data: b"hola mundo".to_vec().try_into().unwrap(),
}; };
let bytes = account.to_bytes(); let bytes = account.to_bytes();
let mut cursor = Cursor::new(bytes.as_ref()); let mut cursor = Cursor::new(bytes.as_ref());

View File

@ -260,7 +260,7 @@ mod tests {
let account = Account { let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337, balance: 1337,
data: vec![0xde, 0xad, 0xbe, 0xef], data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
nonce: 10, nonce: 10,
}; };
@ -275,7 +275,7 @@ mod tests {
let account = Account { let account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337, balance: 1337,
data: vec![0xde, 0xad, 0xbe, 0xef], data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
nonce: 10, nonce: 10,
}; };
@ -290,7 +290,7 @@ mod tests {
let mut account = Account { let mut account = Account {
program_owner: [1, 2, 3, 4, 5, 6, 7, 8], program_owner: [1, 2, 3, 4, 5, 6, 7, 8],
balance: 1337, balance: 1337,
data: vec![0xde, 0xad, 0xbe, 0xef], data: vec![0xde, 0xad, 0xbe, 0xef].try_into().unwrap(),
nonce: 10, nonce: 10,
}; };

View File

@ -1578,6 +1578,7 @@ dependencies = [
"chacha20", "chacha20",
"risc0-zkvm", "risc0-zkvm",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]

View File

@ -63,7 +63,11 @@ fn main() {
let mut pinata_post = pinata.account.clone(); let mut pinata_post = pinata.account.clone();
let mut winner_post = winner.account.clone(); let mut winner_post = winner.account.clone();
pinata_post.balance -= PRIZE; pinata_post.balance -= PRIZE;
pinata_post.data = data.next_data().to_vec(); pinata_post.data = data
.next_data()
.to_vec()
.try_into()
.expect("33 bytes should fit into Data");
winner_post.balance += PRIZE; winner_post.balance += PRIZE;
write_nssa_outputs( write_nssa_outputs(

View File

@ -1,6 +1,9 @@
use nssa_core::program::{ use nssa_core::{
AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs, account::Data,
write_nssa_outputs_with_chained_call, program::{
AccountPostState, ChainedCall, PdaSeed, ProgramInput, read_nssa_inputs,
write_nssa_outputs_with_chained_call,
},
}; };
use risc0_zkvm::{ use risc0_zkvm::{
serde::to_vec, serde::to_vec,
@ -38,11 +41,11 @@ impl Challenge {
digest[..difficulty].iter().all(|&b| b == 0) digest[..difficulty].iter().all(|&b| b == 0)
} }
fn next_data(self) -> [u8; 33] { fn next_data(self) -> Data {
let mut result = [0; 33]; let mut result = [0; 33];
result[0] = self.difficulty; result[0] = self.difficulty;
result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes()); result[1..].copy_from_slice(Impl::hash_bytes(&self.seed).as_bytes());
result result.to_vec().try_into().expect("should fit")
} }
} }
@ -74,7 +77,7 @@ fn main() {
let mut pinata_definition_post = pinata_definition.account.clone(); let mut pinata_definition_post = pinata_definition.account.clone();
let pinata_token_holding_post = pinata_token_holding.account.clone(); let pinata_token_holding_post = pinata_token_holding.account.clone();
let winner_token_holding_post = winner_token_holding.account.clone(); let winner_token_holding_post = winner_token_holding.account.clone();
pinata_definition_post.data = data.next_data().to_vec(); pinata_definition_post.data = data.next_data();
let mut instruction_data: [u8; 23] = [0; 23]; let mut instruction_data: [u8; 23] = [0; 23];
instruction_data[0] = 1; instruction_data[0] = 1;

View File

@ -1,5 +1,5 @@
use nssa_core::{ use nssa_core::{
account::{Account, AccountId, AccountWithMetadata, Data}, account::{Account, AccountId, AccountWithMetadata, Data, data::DATA_MAX_LENGTH_IN_BYTES},
program::{ program::{
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs,
}, },
@ -25,9 +25,11 @@ use nssa_core::{
const TOKEN_DEFINITION_TYPE: u8 = 0; const TOKEN_DEFINITION_TYPE: u8 = 0;
const TOKEN_DEFINITION_DATA_SIZE: usize = 23; const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
const _: () = assert!(TOKEN_DEFINITION_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES);
const TOKEN_HOLDING_TYPE: u8 = 1; const TOKEN_HOLDING_TYPE: u8 = 1;
const TOKEN_HOLDING_DATA_SIZE: usize = 49; const TOKEN_HOLDING_DATA_SIZE: usize = 49;
const _: () = assert!(TOKEN_HOLDING_DATA_SIZE <= DATA_MAX_LENGTH_IN_BYTES);
struct TokenDefinition { struct TokenDefinition {
account_type: u8, account_type: u8,
@ -42,12 +44,15 @@ struct TokenHolding {
} }
impl TokenDefinition { impl TokenDefinition {
fn into_data(self) -> Vec<u8> { fn into_data(self) -> Data {
let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE];
bytes[0] = self.account_type; bytes[0] = self.account_type;
bytes[1..7].copy_from_slice(&self.name); bytes[1..7].copy_from_slice(&self.name);
bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes());
bytes.into() bytes
.to_vec()
.try_into()
.expect("23 bytes should fit into Data")
} }
fn parse(data: &[u8]) -> Option<Self> { fn parse(data: &[u8]) -> Option<Self> {
@ -107,7 +112,10 @@ impl TokenHolding {
bytes[0] = self.account_type; bytes[0] = self.account_type;
bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); bytes[1..33].copy_from_slice(&self.definition_id.to_bytes());
bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); bytes[33..].copy_from_slice(&self.balance.to_le_bytes());
bytes.into() bytes
.to_vec()
.try_into()
.expect("33 bytes should fit into Data")
} }
} }
@ -398,15 +406,15 @@ mod tests {
let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10);
let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); let [definition_account, holding_account] = post_states.try_into().ok().unwrap();
assert_eq!( assert_eq!(
definition_account.account().data, definition_account.account().data.as_ref(),
vec![ &[
0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0 0, 0
] ]
); );
assert_eq!( assert_eq!(
holding_account.account().data, holding_account.account().data.as_ref(),
vec![ &[
1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0 0, 0
@ -456,7 +464,9 @@ mod tests {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
// First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts
data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE], data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE]
.try_into()
.unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -478,7 +488,7 @@ mod tests {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
// Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE`
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1], data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1].try_into().unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -500,7 +510,7 @@ mod tests {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
// Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE`
data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1], data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1].try_into().unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -521,7 +531,7 @@ mod tests {
let pre_states = vec![ let pre_states = vec![
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
data: vec![1; TOKEN_HOLDING_DATA_SIZE], data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -529,10 +539,12 @@ mod tests {
}, },
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
data: vec![1] data: [1]
.into_iter() .into_iter()
.chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1])
.collect(), .collect::<Vec<_>>()
.try_into()
.unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -549,10 +561,12 @@ mod tests {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
// Account with balance 37 // Account with balance 37
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter() .into_iter()
.chain(u128::to_le_bytes(37)) .chain(u128::to_le_bytes(37))
.collect(), .collect::<Vec<_>>()
.try_into()
.unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -560,7 +574,7 @@ mod tests {
}, },
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
data: vec![1; TOKEN_HOLDING_DATA_SIZE], data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -578,10 +592,12 @@ mod tests {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
// Account with balance 37 // Account with balance 37
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter() .into_iter()
.chain(u128::to_le_bytes(37)) .chain(u128::to_le_bytes(37))
.collect(), .collect::<Vec<_>>()
.try_into()
.unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: false, is_authorized: false,
@ -589,7 +605,7 @@ mod tests {
}, },
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
data: vec![1; TOKEN_HOLDING_DATA_SIZE], data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -605,10 +621,12 @@ mod tests {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
// Account with balance 37 // Account with balance 37
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter() .into_iter()
.chain(u128::to_le_bytes(37)) .chain(u128::to_le_bytes(37))
.collect(), .collect::<Vec<_>>()
.try_into()
.unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -617,10 +635,12 @@ mod tests {
AccountWithMetadata { AccountWithMetadata {
account: Account { account: Account {
// Account with balance 255 // Account with balance 255
data: vec![1; TOKEN_HOLDING_DATA_SIZE - 16] data: [1; TOKEN_HOLDING_DATA_SIZE - 16]
.into_iter() .into_iter()
.chain(u128::to_le_bytes(255)) .chain(u128::to_le_bytes(255))
.collect(), .collect::<Vec<_>>()
.try_into()
.unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: true, is_authorized: true,
@ -630,15 +650,15 @@ mod tests {
let post_states = transfer(&pre_states, 11); let post_states = transfer(&pre_states, 11);
let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); let [sender_post, recipient_post] = post_states.try_into().ok().unwrap();
assert_eq!( assert_eq!(
sender_post.account().data, sender_post.account().data.as_ref(),
vec![ [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
); );
assert_eq!( assert_eq!(
recipient_post.account().data, recipient_post.account().data.as_ref(),
vec![ [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]
@ -654,7 +674,9 @@ mod tests {
data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] data: [0; TOKEN_DEFINITION_DATA_SIZE - 16]
.into_iter() .into_iter()
.chain(u128::to_le_bytes(1000)) .chain(u128::to_le_bytes(1000))
.collect(), .collect::<Vec<_>>()
.try_into()
.unwrap(),
..Account::default() ..Account::default()
}, },
is_authorized: false, is_authorized: false,
@ -668,10 +690,13 @@ mod tests {
]; ];
let post_states = initialize_account(&pre_states); let post_states = initialize_account(&pre_states);
let [definition, holding] = post_states.try_into().ok().unwrap(); let [definition, holding] = post_states.try_into().ok().unwrap();
assert_eq!(definition.account().data, pre_states[0].account.data);
assert_eq!( assert_eq!(
holding.account().data, definition.account().data.as_ref(),
vec![ pre_states[0].account.data.as_ref()
);
assert_eq!(
holding.account().data.as_ref(),
[
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
] ]

View File

@ -95,7 +95,7 @@ impl Proof {
mod tests { mod tests {
use nssa_core::{ use nssa_core::{
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier,
account::{Account, AccountId, AccountWithMetadata}, account::{Account, AccountId, AccountWithMetadata, data::Data},
}; };
use super::*; use super::*;
@ -134,14 +134,14 @@ mod tests {
program_owner: program.id(), program_owner: program.id(),
balance: 100 - balance_to_move, balance: 100 - balance_to_move,
nonce: 1, nonce: 1,
data: vec![], data: Data::default(),
}; };
let expected_recipient_post = Account { let expected_recipient_post = Account {
program_owner: program.id(), program_owner: program.id(),
balance: balance_to_move, balance: balance_to_move,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
data: vec![], data: Data::default(),
}; };
let expected_sender_pre = sender.clone(); let expected_sender_pre = sender.clone();
@ -191,7 +191,7 @@ mod tests {
balance: 100, balance: 100,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
program_owner: program.id(), program_owner: program.id(),
data: vec![], data: Data::default(),
}, },
true, true,
AccountId::from(&sender_keys.npk()), AccountId::from(&sender_keys.npk()),

View File

@ -234,7 +234,7 @@ impl V02State {
program_owner: Program::pinata().id(), program_owner: Program::pinata().id(),
balance: 1500, balance: 1500,
// Difficulty: 3 // Difficulty: 3
data: vec![3; 33], data: vec![3; 33].try_into().unwrap(),
nonce: 0, nonce: 0,
}, },
); );
@ -248,7 +248,7 @@ impl V02State {
Account { Account {
program_owner: Program::pinata_token().id(), program_owner: Program::pinata_token().id(),
// Difficulty: 3 // Difficulty: 3
data: vec![3; 33], data: vec![3; 33].try_into().expect("should fit"),
..Account::default() ..Account::default()
}, },
); );
@ -262,7 +262,7 @@ pub mod tests {
use nssa_core::{ use nssa_core::{
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce}, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar}, encryption::{EphemeralPublicKey, IncomingViewingPublicKey, Scalar},
program::{PdaSeed, ProgramId}, program::{PdaSeed, ProgramId},
}; };
@ -505,7 +505,7 @@ pub mod tests {
..Account::default() ..Account::default()
}; };
let account_with_default_values_except_data = Account { let account_with_default_values_except_data = Account {
data: vec![0xca, 0xfe], data: vec![0xca, 0xfe].try_into().unwrap(),
..Account::default() ..Account::default()
}; };
self.force_insert_account( self.force_insert_account(
@ -1027,7 +1027,7 @@ pub mod tests {
program_owner: Program::authenticated_transfer_program().id(), program_owner: Program::authenticated_transfer_program().id(),
balance: 100, balance: 100,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
data: vec![], data: Data::default(),
}; };
let recipient_keys = test_private_account_keys_2(); let recipient_keys = test_private_account_keys_2();
@ -1051,7 +1051,7 @@ pub mod tests {
program_owner: Program::authenticated_transfer_program().id(), program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xcafecafe, nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move, balance: sender_private_account.balance - balance_to_move,
data: vec![], data: Data::default(),
}, },
); );
@ -1093,7 +1093,7 @@ pub mod tests {
program_owner: Program::authenticated_transfer_program().id(), program_owner: Program::authenticated_transfer_program().id(),
balance: 100, balance: 100,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
data: vec![], data: Data::default(),
}; };
let recipient_keys = test_public_account_keys_1(); let recipient_keys = test_public_account_keys_1();
let recipient_initial_balance = 400; let recipient_initial_balance = 400;
@ -1126,7 +1126,7 @@ pub mod tests {
program_owner: Program::authenticated_transfer_program().id(), program_owner: Program::authenticated_transfer_program().id(),
nonce: 0xcafecafe, nonce: 0xcafecafe,
balance: sender_private_account.balance - balance_to_move, balance: sender_private_account.balance - balance_to_move,
data: vec![], data: Data::default(),
}, },
); );
@ -1692,7 +1692,7 @@ pub mod tests {
let private_account_2 = AccountWithMetadata::new( let private_account_2 = AccountWithMetadata::new(
Account { Account {
// Non default data // Non default data
data: b"hola mundo".to_vec(), data: b"hola mundo".to_vec().try_into().unwrap(),
..Account::default() ..Account::default()
}, },
false, false,
@ -1981,7 +1981,7 @@ pub mod tests {
program_owner: Program::authenticated_transfer_program().id(), program_owner: Program::authenticated_transfer_program().id(),
balance: 100, balance: 100,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
data: vec![], data: Data::default(),
}; };
let recipient_keys = test_private_account_keys_2(); let recipient_keys = test_private_account_keys_2();
@ -2007,7 +2007,7 @@ pub mod tests {
program_owner: Program::authenticated_transfer_program().id(), program_owner: Program::authenticated_transfer_program().id(),
balance: 100 - balance_to_move, balance: 100 - balance_to_move,
nonce: 0xcafecafe, nonce: 0xcafecafe,
data: vec![], data: Data::default(),
}; };
let tx = private_balance_transfer_for_tests( let tx = private_balance_transfer_for_tests(
@ -2298,7 +2298,7 @@ pub mod tests {
expected_winner_account_data[33..].copy_from_slice(&150u128.to_le_bytes()); expected_winner_account_data[33..].copy_from_slice(&150u128.to_le_bytes());
let expected_winner_token_holding_post = Account { let expected_winner_token_holding_post = Account {
program_owner: token.id(), program_owner: token.id(),
data: expected_winner_account_data.to_vec(), data: expected_winner_account_data.to_vec().try_into().unwrap(),
..Account::default() ..Account::default()
}; };

View File

@ -1583,6 +1583,7 @@ dependencies = [
"chacha20", "chacha20",
"risc0-zkvm", "risc0-zkvm",
"serde", "serde",
"thiserror",
] ]
[[package]] [[package]]

View File

@ -12,7 +12,9 @@ fn main() {
let account_pre = &pre.account; let account_pre = &pre.account;
let mut account_post = account_pre.clone(); let mut account_post = account_pre.clone();
account_post.data.push(0); let mut data_vec = account_post.data.into_inner();
data_vec.push(0);
account_post.data = data_vec.try_into().expect("data_vec should fit into Data");
write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]); write_nssa_outputs(vec![pre], vec![AccountPostState::new_claimed(account_post)]);
} }

View File

@ -197,6 +197,7 @@ async fn find_solution(wallet: &WalletCore, pinata_account_id: nssa::AccountId)
let account = wallet.get_account_public(pinata_account_id).await?; let account = wallet.get_account_public(pinata_account_id).await?;
let data: [u8; 33] = account let data: [u8; 33] = account
.data .data
.as_ref()
.try_into() .try_into()
.map_err(|_| anyhow::Error::msg("invalid pinata account data"))?; .map_err(|_| anyhow::Error::msg("invalid pinata account data"))?;