mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-14 12:09:35 +00:00
Merge branch 'main' into marvin/pq-public-keys
This commit is contained in:
commit
900b7b8d53
31
Cargo.lock
generated
31
Cargo.lock
generated
@ -1019,19 +1019,12 @@ version = "0.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitcoin-io"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitcoin_hashes"
|
name = "bitcoin_hashes"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b"
|
checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin-io",
|
|
||||||
"hex-conservative",
|
"hex-conservative",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1522,6 +1515,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"logos-blockchain-common-http-client",
|
"logos-blockchain-common-http-client",
|
||||||
"nssa",
|
"nssa",
|
||||||
|
"nssa_core",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"sha2",
|
"sha2",
|
||||||
@ -3977,7 +3971,6 @@ dependencies = [
|
|||||||
"nssa",
|
"nssa",
|
||||||
"nssa_core",
|
"nssa_core",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"secp256k1",
|
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
@ -5269,13 +5262,13 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"hex",
|
"hex",
|
||||||
"hex-literal 1.1.0",
|
"hex-literal 1.1.0",
|
||||||
|
"k256",
|
||||||
"log",
|
"log",
|
||||||
"nssa_core",
|
"nssa_core",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"risc0-binfmt",
|
"risc0-binfmt",
|
||||||
"risc0-build",
|
"risc0-build",
|
||||||
"risc0-zkvm",
|
"risc0-zkvm",
|
||||||
"secp256k1",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"sha2",
|
"sha2",
|
||||||
@ -7086,26 +7079,6 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "secp256k1"
|
|
||||||
version = "0.31.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2"
|
|
||||||
dependencies = [
|
|
||||||
"bitcoin_hashes",
|
|
||||||
"rand 0.9.2",
|
|
||||||
"secp256k1-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "secp256k1-sys"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.7.0"
|
version = "3.7.0"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -9,6 +9,7 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nssa.workspace = true
|
nssa.workspace = true
|
||||||
|
nssa_core.workspace = true
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
|
use nssa_core::{BlockId, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest as _, Sha256, digest::FixedOutput as _};
|
use sha2::{Digest as _, Sha256, digest::FixedOutput as _};
|
||||||
|
|
||||||
@ -6,8 +7,6 @@ use crate::{HashType, transaction::NSSATransaction};
|
|||||||
|
|
||||||
pub type MantleMsgId = [u8; 32];
|
pub type MantleMsgId = [u8; 32];
|
||||||
pub type BlockHash = HashType;
|
pub type BlockHash = HashType;
|
||||||
pub type BlockId = u64;
|
|
||||||
pub type TimeStamp = u64;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
|
||||||
pub struct BlockMeta {
|
pub struct BlockMeta {
|
||||||
@ -35,7 +34,7 @@ pub struct BlockHeader {
|
|||||||
pub block_id: BlockId,
|
pub block_id: BlockId,
|
||||||
pub prev_block_hash: BlockHash,
|
pub prev_block_hash: BlockHash,
|
||||||
pub hash: BlockHash,
|
pub hash: BlockHash,
|
||||||
pub timestamp: TimeStamp,
|
pub timestamp: Timestamp,
|
||||||
pub signature: nssa::Signature,
|
pub signature: nssa::Signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +74,7 @@ impl<'de> Deserialize<'de> for Block {
|
|||||||
pub struct HashableBlockData {
|
pub struct HashableBlockData {
|
||||||
pub block_id: BlockId,
|
pub block_id: BlockId,
|
||||||
pub prev_block_hash: BlockHash,
|
pub prev_block_hash: BlockHash,
|
||||||
pub timestamp: TimeStamp,
|
pub timestamp: Timestamp,
|
||||||
pub transactions: Vec<NSSATransaction>,
|
pub transactions: Vec<NSSATransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use nssa::{AccountId, V03State};
|
use nssa::{AccountId, V03State};
|
||||||
|
use nssa_core::{BlockId, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{HashType, block::BlockId};
|
use crate::HashType;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum NSSATransaction {
|
pub enum NSSATransaction {
|
||||||
@ -69,11 +70,12 @@ impl NSSATransaction {
|
|||||||
self,
|
self,
|
||||||
state: &mut V03State,
|
state: &mut V03State,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
|
timestamp: Timestamp,
|
||||||
) -> Result<Self, nssa::error::NssaError> {
|
) -> Result<Self, nssa::error::NssaError> {
|
||||||
match &self {
|
match &self {
|
||||||
Self::Public(tx) => state.transition_from_public_transaction(tx, block_id),
|
Self::Public(tx) => state.transition_from_public_transaction(tx, block_id, timestamp),
|
||||||
Self::PrivacyPreserving(tx) => {
|
Self::PrivacyPreserving(tx) => {
|
||||||
state.transition_from_privacy_preserving_transaction(tx, block_id)
|
state.transition_from_privacy_preserving_transaction(tx, block_id, timestamp)
|
||||||
}
|
}
|
||||||
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
|
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hello-world example program.
|
// Hello-world example program.
|
||||||
//
|
//
|
||||||
@ -45,13 +43,7 @@ fn main() {
|
|||||||
|
|
||||||
// Wrap the post state account values inside a `AccountPostState` instance.
|
// Wrap the post state account values inside a `AccountPostState` instance.
|
||||||
// This is used to forward the account claiming request if any
|
// This is used to forward the account claiming request if any
|
||||||
let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
let post_state = AccountPostState::new_claimed_if_default(post_account, Claim::Authorized);
|
||||||
// This produces a claim request
|
|
||||||
AccountPostState::new_claimed(post_account)
|
|
||||||
} else {
|
|
||||||
// This doesn't produce a claim request
|
|
||||||
AccountPostState::new(post_account)
|
|
||||||
};
|
|
||||||
|
|
||||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||||
// with the previous values of the accounts, and the transition to the post states conforms
|
// with the previous values of the accounts, and the transition to the post states conforms
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Hello-world with authorization example program.
|
// Hello-world with authorization example program.
|
||||||
//
|
//
|
||||||
@ -52,13 +50,7 @@ fn main() {
|
|||||||
|
|
||||||
// Wrap the post state account values inside a `AccountPostState` instance.
|
// Wrap the post state account values inside a `AccountPostState` instance.
|
||||||
// This is used to forward the account claiming request if any
|
// This is used to forward the account claiming request if any
|
||||||
let post_state = if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
let post_state = AccountPostState::new_claimed_if_default(post_account, Claim::Authorized);
|
||||||
// This produces a claim request
|
|
||||||
AccountPostState::new_claimed(post_account)
|
|
||||||
} else {
|
|
||||||
// This doesn't produce a claim request
|
|
||||||
AccountPostState::new(post_account)
|
|
||||||
};
|
|
||||||
|
|
||||||
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
// The output is a proposed state difference. It will only succeed if the pre states coincide
|
||||||
// with the previous values of the accounts, and the transition to the post states conforms
|
// with the previous values of the accounts, and the transition to the post states conforms
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{AccountWithMetadata, Data},
|
||||||
program::{
|
program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs},
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hello-world with write + move_data example program.
|
// Hello-world with write + move_data example program.
|
||||||
@ -26,16 +24,6 @@ const MOVE_DATA_FUNCTION_ID: u8 = 1;
|
|||||||
|
|
||||||
type Instruction = (u8, Vec<u8>);
|
type Instruction = (u8, Vec<u8>);
|
||||||
|
|
||||||
fn build_post_state(post_account: Account) -> AccountPostState {
|
|
||||||
if post_account.program_owner == DEFAULT_PROGRAM_ID {
|
|
||||||
// This produces a claim request
|
|
||||||
AccountPostState::new_claimed(post_account)
|
|
||||||
} else {
|
|
||||||
// This doesn't produce a claim request
|
|
||||||
AccountPostState::new(post_account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(pre_state: AccountWithMetadata, greeting: &[u8]) -> AccountPostState {
|
fn write(pre_state: AccountWithMetadata, greeting: &[u8]) -> AccountPostState {
|
||||||
// Construct the post state account values
|
// Construct the post state account values
|
||||||
let post_account = {
|
let post_account = {
|
||||||
@ -48,7 +36,7 @@ fn write(pre_state: AccountWithMetadata, greeting: &[u8]) -> AccountPostState {
|
|||||||
this
|
this
|
||||||
};
|
};
|
||||||
|
|
||||||
build_post_state(post_account)
|
AccountPostState::new_claimed_if_default(post_account, Claim::Authorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_data(from_pre: AccountWithMetadata, to_pre: AccountWithMetadata) -> Vec<AccountPostState> {
|
fn move_data(from_pre: AccountWithMetadata, to_pre: AccountWithMetadata) -> Vec<AccountPostState> {
|
||||||
@ -58,7 +46,7 @@ fn move_data(from_pre: AccountWithMetadata, to_pre: AccountWithMetadata) -> Vec<
|
|||||||
let from_post = {
|
let from_post = {
|
||||||
let mut this = from_pre.account;
|
let mut this = from_pre.account;
|
||||||
this.data = Data::default();
|
this.data = Data::default();
|
||||||
build_post_state(this)
|
AccountPostState::new_claimed_if_default(this, Claim::Authorized)
|
||||||
};
|
};
|
||||||
|
|
||||||
let to_post = {
|
let to_post = {
|
||||||
@ -68,7 +56,7 @@ fn move_data(from_pre: AccountWithMetadata, to_pre: AccountWithMetadata) -> Vec<
|
|||||||
this.data = bytes
|
this.data = bytes
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("Data should fit within the allowed limits");
|
.expect("Data should fit within the allowed limits");
|
||||||
build_post_state(this)
|
AccountPostState::new_claimed_if_default(this, Claim::Authorized)
|
||||||
};
|
};
|
||||||
|
|
||||||
vec![from_post, to_post]
|
vec![from_post, to_post]
|
||||||
|
|||||||
@ -177,13 +177,13 @@ pub fn TransactionPage() -> impl IntoView {
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
validity_window
|
block_validity_window,
|
||||||
|
timestamp_validity_window,
|
||||||
} = message;
|
} = message;
|
||||||
let WitnessSet {
|
let WitnessSet {
|
||||||
signatures_and_public_keys: _,
|
signatures_and_public_keys: _,
|
||||||
proof,
|
proof,
|
||||||
} = witness_set;
|
} = witness_set;
|
||||||
|
|
||||||
let proof_len = proof.map_or(0, |p| p.0.len());
|
let proof_len = proof.map_or(0, |p| p.0.len());
|
||||||
view! {
|
view! {
|
||||||
<div class="transaction-details">
|
<div class="transaction-details">
|
||||||
@ -214,8 +214,12 @@ pub fn TransactionPage() -> impl IntoView {
|
|||||||
<span class="info-value">{format!("{proof_len} bytes")}</span>
|
<span class="info-value">{format!("{proof_len} bytes")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<span class="info-label">"Validity Window:"</span>
|
<span class="info-label">"Block Validity Window:"</span>
|
||||||
<span class="info-value">{validity_window.to_string()}</span>
|
<span class="info-value">{block_validity_window.to_string()}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">"Timestamp Validity Window:"</span>
|
||||||
|
<span class="info-value">{timestamp_validity_window.to_string()}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,11 @@ use std::{path::Path, sync::Arc};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bedrock_client::HeaderId;
|
use bedrock_client::HeaderId;
|
||||||
use common::{
|
use common::{
|
||||||
block::{BedrockStatus, Block, BlockId},
|
block::{BedrockStatus, Block},
|
||||||
transaction::NSSATransaction,
|
transaction::NSSATransaction,
|
||||||
};
|
};
|
||||||
use nssa::{Account, AccountId, V03State};
|
use nssa::{Account, AccountId, V03State};
|
||||||
|
use nssa_core::BlockId;
|
||||||
use storage::indexer::RocksDBIO;
|
use storage::indexer::RocksDBIO;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
@ -125,7 +126,11 @@ impl IndexerStore {
|
|||||||
transaction
|
transaction
|
||||||
.clone()
|
.clone()
|
||||||
.transaction_stateless_check()?
|
.transaction_stateless_check()?
|
||||||
.execute_check_on_state(&mut state_guard, block.header.block_id)?;
|
.execute_check_on_state(
|
||||||
|
&mut state_guard,
|
||||||
|
block.header.block_id,
|
||||||
|
block.header.timestamp,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -287,7 +287,8 @@ impl From<nssa::privacy_preserving_transaction::message::Message> for PrivacyPre
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
validity_window,
|
block_validity_window,
|
||||||
|
timestamp_validity_window,
|
||||||
} = value;
|
} = value;
|
||||||
Self {
|
Self {
|
||||||
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
||||||
@ -302,7 +303,8 @@ impl From<nssa::privacy_preserving_transaction::message::Message> for PrivacyPre
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(n, d)| (n.into(), d.into()))
|
.map(|(n, d)| (n.into(), d.into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
validity_window: validity_window.into(),
|
block_validity_window: block_validity_window.into(),
|
||||||
|
timestamp_validity_window: timestamp_validity_window.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,7 +320,8 @@ impl TryFrom<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction:
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
validity_window,
|
block_validity_window,
|
||||||
|
timestamp_validity_window,
|
||||||
} = value;
|
} = value;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
public_account_ids: public_account_ids.into_iter().map(Into::into).collect(),
|
||||||
@ -340,7 +343,10 @@ impl TryFrom<PrivacyPreservingMessage> for nssa::privacy_preserving_transaction:
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(n, d)| (n.into(), d.into()))
|
.map(|(n, d)| (n.into(), d.into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
validity_window: validity_window
|
block_validity_window: block_validity_window
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?,
|
||||||
|
timestamp_validity_window: timestamp_validity_window
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?,
|
.map_err(|e| nssa::error::NssaError::InvalidInput(format!("{e}")))?,
|
||||||
})
|
})
|
||||||
@ -692,13 +698,13 @@ impl From<HashType> for common::HashType {
|
|||||||
// ValidityWindow conversions
|
// ValidityWindow conversions
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
impl From<nssa_core::program::ValidityWindow> for ValidityWindow {
|
impl From<nssa_core::program::ValidityWindow<u64>> for ValidityWindow {
|
||||||
fn from(value: nssa_core::program::ValidityWindow) -> Self {
|
fn from(value: nssa_core::program::ValidityWindow<u64>) -> Self {
|
||||||
Self((value.start(), value.end()))
|
Self((value.start(), value.end()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ValidityWindow> for nssa_core::program::ValidityWindow {
|
impl TryFrom<ValidityWindow> for nssa_core::program::ValidityWindow<u64> {
|
||||||
type Error = nssa_core::program::InvalidWindow;
|
type Error = nssa_core::program::InvalidWindow;
|
||||||
|
|
||||||
fn try_from(value: ValidityWindow) -> Result<Self, Self::Error> {
|
fn try_from(value: ValidityWindow) -> Result<Self, Self::Error> {
|
||||||
|
|||||||
@ -235,7 +235,8 @@ pub struct PrivacyPreservingMessage {
|
|||||||
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
||||||
pub new_commitments: Vec<Commitment>,
|
pub new_commitments: Vec<Commitment>,
|
||||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||||
pub validity_window: ValidityWindow,
|
pub block_validity_window: ValidityWindow,
|
||||||
|
pub timestamp_validity_window: ValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
|
||||||
|
|||||||
@ -124,7 +124,8 @@ impl MockIndexerService {
|
|||||||
indexer_service_protocol::Nullifier([tx_idx as u8; 32]),
|
indexer_service_protocol::Nullifier([tx_idx as u8; 32]),
|
||||||
CommitmentSetDigest([0xff; 32]),
|
CommitmentSetDigest([0xff; 32]),
|
||||||
)],
|
)],
|
||||||
validity_window: ValidityWindow((None, None)),
|
block_validity_window: ValidityWindow((None, None)),
|
||||||
|
timestamp_validity_window: ValidityWindow((None, None)),
|
||||||
},
|
},
|
||||||
witness_set: WitnessSet {
|
witness_set: WitnessSet {
|
||||||
signatures_and_public_keys: vec![],
|
signatures_and_public_keys: vec![],
|
||||||
|
|||||||
@ -11,10 +11,13 @@ use integration_tests::{
|
|||||||
NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
|
NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext,
|
||||||
};
|
};
|
||||||
use log::info;
|
use log::info;
|
||||||
use nssa::{AccountId, program::Program};
|
use nssa::program::Program;
|
||||||
use sequencer_service_rpc::RpcClient as _;
|
use sequencer_service_rpc::RpcClient as _;
|
||||||
use tokio::test;
|
use tokio::test;
|
||||||
use wallet::cli::Command;
|
use wallet::cli::{
|
||||||
|
Command, SubcommandReturnValue,
|
||||||
|
account::{AccountSubcommand, NewSubcommand},
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
async fn deploy_and_execute_program() -> Result<()> {
|
async fn deploy_and_execute_program() -> Result<()> {
|
||||||
@ -40,14 +43,31 @@ async fn deploy_and_execute_program() -> Result<()> {
|
|||||||
// logic)
|
// logic)
|
||||||
let bytecode = std::fs::read(binary_filepath)?;
|
let bytecode = std::fs::read(binary_filepath)?;
|
||||||
let data_changer = Program::new(bytecode)?;
|
let data_changer = Program::new(bytecode)?;
|
||||||
let account_id: AccountId = "11".repeat(16).parse()?;
|
|
||||||
|
let SubcommandReturnValue::RegisterAccount { account_id } = wallet::cli::execute_subcommand(
|
||||||
|
ctx.wallet_mut(),
|
||||||
|
Command::Account(AccountSubcommand::New(NewSubcommand::Public {
|
||||||
|
cci: None,
|
||||||
|
label: None,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
panic!("Expected RegisterAccount return value");
|
||||||
|
};
|
||||||
|
|
||||||
|
let nonces = ctx.wallet().get_accounts_nonces(vec![account_id]).await?;
|
||||||
|
let private_key = ctx
|
||||||
|
.wallet()
|
||||||
|
.get_account_public_signing_key(account_id)
|
||||||
|
.unwrap();
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
data_changer.id(),
|
data_changer.id(),
|
||||||
vec![account_id],
|
vec![account_id],
|
||||||
vec![],
|
nonces,
|
||||||
vec![0],
|
vec![0],
|
||||||
)?;
|
)?;
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[private_key]);
|
||||||
let transaction = nssa::PublicTransaction::new(message, witness_set);
|
let transaction = nssa::PublicTransaction::new(message, witness_set);
|
||||||
let _response = ctx
|
let _response = ctx
|
||||||
.sequencer_client()
|
.sequencer_client()
|
||||||
@ -64,7 +84,7 @@ async fn deploy_and_execute_program() -> Result<()> {
|
|||||||
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.as_ref(), &[0]);
|
assert_eq!(post_state_account.data.as_ref(), &[0]);
|
||||||
assert_eq!(post_state_account.nonce.0, 0);
|
assert_eq!(post_state_account.nonce.0, 1);
|
||||||
|
|
||||||
info!("Successfully deployed and executed program");
|
info!("Successfully deployed and executed program");
|
||||||
|
|
||||||
|
|||||||
@ -924,7 +924,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
|||||||
let home = tempfile::tempdir()?;
|
let home = tempfile::tempdir()?;
|
||||||
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?;
|
||||||
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
|
let from: FfiBytes32 = (&ctx.ctx().existing_private_accounts()[0]).into();
|
||||||
let to = FfiBytes32::from_bytes([37; 32]);
|
let to: FfiBytes32 = (&ctx.ctx().existing_public_accounts()[0]).into();
|
||||||
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
let amount: [u8; 16] = 100_u128.to_le_bytes();
|
||||||
|
|
||||||
let mut transfer_result = FfiTransferResult::default();
|
let mut transfer_result = FfiTransferResult::default();
|
||||||
@ -967,7 +967,7 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(from_balance, 9900);
|
assert_eq!(from_balance, 9900);
|
||||||
assert_eq!(to_balance, 100);
|
assert_eq!(to_balance, 10100);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
wallet_ffi_free_transfer_result(&raw mut transfer_result);
|
||||||
|
|||||||
@ -8,8 +8,6 @@ license = { workspace = true }
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
secp256k1 = "0.31.1"
|
|
||||||
|
|
||||||
nssa.workspace = true
|
nssa.workspace = true
|
||||||
nssa_core.workspace = true
|
nssa_core.workspace = true
|
||||||
common.workspace = true
|
common.workspace = true
|
||||||
|
|||||||
@ -137,11 +137,12 @@ impl<'a> From<&'a mut ChildKeysPrivate> for &'a mut (KeyChain, nssa::Account) {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use nssa_core::NullifierSecretKey;
|
use nssa_core::{NullifierPublicKey, NullifierSecretKey};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::key_management::{self, secret_holders::ViewingSecretKey};
|
use crate::key_management::{self, secret_holders::ViewingSecretKey};
|
||||||
|
|
||||||
|
#[expect(clippy::redundant_type_annotations, reason = "TODO: clippy requires")]
|
||||||
#[test]
|
#[test]
|
||||||
fn master_key_generation() {
|
fn master_key_generation() {
|
||||||
let seed: [u8; 64] = [
|
let seed: [u8; 64] = [
|
||||||
@ -153,7 +154,7 @@ mod tests {
|
|||||||
|
|
||||||
let keys = ChildKeysPrivate::root(seed);
|
let keys = ChildKeysPrivate::root(seed);
|
||||||
|
|
||||||
let expected_ssk = key_management::secret_holders::SecretSpendingKey([
|
let expected_ssk: SecretSpendingKey = key_management::secret_holders::SecretSpendingKey([
|
||||||
246, 79, 26, 124, 135, 95, 52, 51, 201, 27, 48, 194, 2, 144, 51, 219, 245, 128, 139,
|
246, 79, 26, 124, 135, 95, 52, 51, 201, 27, 48, 194, 2, 144, 51, 219, 245, 128, 139,
|
||||||
222, 42, 195, 105, 33, 115, 97, 186, 0, 97, 14, 218, 191,
|
222, 42, 195, 105, 33, 115, 97, 186, 0, 97, 14, 218, 191,
|
||||||
]);
|
]);
|
||||||
@ -168,7 +169,7 @@ mod tests {
|
|||||||
34, 234, 19, 222, 2, 22, 12, 163, 252, 88, 11, 0, 163,
|
34, 234, 19, 222, 2, 22, 12, 163, 252, 88, 11, 0, 163,
|
||||||
];
|
];
|
||||||
|
|
||||||
let expected_npk = nssa_core::NullifierPublicKey([
|
let expected_npk: NullifierPublicKey = nssa_core::NullifierPublicKey([
|
||||||
7, 123, 125, 191, 233, 183, 201, 4, 20, 214, 155, 210, 45, 234, 27, 240, 194, 111, 97,
|
7, 123, 125, 191, 233, 183, 201, 4, 20, 214, 155, 210, 45, 234, 27, 240, 194, 111, 97,
|
||||||
247, 155, 113, 122, 246, 192, 0, 70, 61, 76, 71, 70, 2,
|
247, 155, 113, 122, 246, 192, 0, 70, 61, 76, 71, 70, 2,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use secp256k1::Scalar;
|
use k256::elliptic_curve::{PrimeField as _, sec1::ToEncodedPoint as _};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::key_management::key_tree::traits::KeyNode;
|
use crate::key_management::key_tree::traits::KeyNode;
|
||||||
@ -14,7 +14,6 @@ pub struct ChildKeysPublic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ChildKeysPublic {
|
impl ChildKeysPublic {
|
||||||
#[expect(clippy::big_endian_bytes, reason = "BIP-032 uses big endian")]
|
|
||||||
fn compute_hash_value(&self, cci: u32) -> [u8; 64] {
|
fn compute_hash_value(&self, cci: u32) -> [u8; 64] {
|
||||||
let mut hash_input = vec![];
|
let mut hash_input = vec![];
|
||||||
|
|
||||||
@ -22,16 +21,17 @@ impl ChildKeysPublic {
|
|||||||
// Non-harden.
|
// Non-harden.
|
||||||
// BIP-032 compatibility requires 1-byte header from the public_key;
|
// BIP-032 compatibility requires 1-byte header from the public_key;
|
||||||
// Not stored in `self.cpk.value()`.
|
// Not stored in `self.cpk.value()`.
|
||||||
let sk = secp256k1::SecretKey::from_byte_array(*self.cssk.value())
|
let sk = k256::SecretKey::from_bytes(self.csk.value().into())
|
||||||
.expect("32 bytes, within curve order");
|
.expect("32 bytes, within curve order");
|
||||||
let pk = secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &sk);
|
let pk = sk.public_key();
|
||||||
hash_input.extend_from_slice(&secp256k1::PublicKey::serialize(&pk));
|
hash_input.extend_from_slice(pk.to_encoded_point(true).as_bytes());
|
||||||
} else {
|
} else {
|
||||||
// Harden.
|
// Harden.
|
||||||
hash_input.extend_from_slice(&[0_u8]);
|
hash_input.extend_from_slice(&[0_u8]);
|
||||||
hash_input.extend_from_slice(self.cssk.value());
|
hash_input.extend_from_slice(self.cssk.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::big_endian_bytes, reason = "BIP-032 uses big endian")]
|
||||||
hash_input.extend_from_slice(&cci.to_be_bytes());
|
hash_input.extend_from_slice(&cci.to_be_bytes());
|
||||||
|
|
||||||
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
hmac_sha512::HMAC::mac(hash_input, self.ccc)
|
||||||
@ -42,8 +42,14 @@ impl KeyNode for ChildKeysPublic {
|
|||||||
fn root(seed: [u8; 64]) -> Self {
|
fn root(seed: [u8; 64]) -> Self {
|
||||||
let hash_value = hmac_sha512::HMAC::mac(seed, "LEE_master_pub");
|
let hash_value = hmac_sha512::HMAC::mac(seed, "LEE_master_pub");
|
||||||
|
|
||||||
let cssk = nssa::PrivateKey::try_new(*hash_value.first_chunk::<32>().unwrap()).unwrap();
|
let cssk = nssa::PrivateKey::try_new(
|
||||||
|
*hash_value
|
||||||
|
.first_chunk::<32>()
|
||||||
|
.expect("hash_value is 64 bytes, must be safe to get first 32"),
|
||||||
|
)
|
||||||
|
.expect("Expect a valid Private Key");
|
||||||
let csk = nssa::PrivateKey::tweak(cssk.value()).unwrap();
|
let csk = nssa::PrivateKey::tweak(cssk.value()).unwrap();
|
||||||
|
|
||||||
let ccc = *hash_value.last_chunk::<32>().unwrap();
|
let ccc = *hash_value.last_chunk::<32>().unwrap();
|
||||||
let cpk = nssa::PublicKey::new_from_private_key(&csk);
|
let cpk = nssa::PublicKey::new_from_private_key(&csk);
|
||||||
|
|
||||||
@ -65,7 +71,7 @@ impl KeyNode for ChildKeysPublic {
|
|||||||
.expect("hash_value is 64 bytes, must be safe to get first 32"),
|
.expect("hash_value is 64 bytes, must be safe to get first 32"),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
/*
|
||||||
let cssk = nssa::PrivateKey::try_new({
|
let cssk = nssa::PrivateKey::try_new({
|
||||||
cssk.add_tweak(&Scalar::from_be_bytes(*self.cssk.value()).unwrap())
|
cssk.add_tweak(&Scalar::from_be_bytes(*self.cssk.value()).unwrap())
|
||||||
.expect("Expect a valid Scalar")
|
.expect("Expect a valid Scalar")
|
||||||
@ -79,6 +85,21 @@ impl KeyNode for ChildKeysPublic {
|
|||||||
secp256k1::constants::CURVE_ORDER >= *csk.value(),
|
secp256k1::constants::CURVE_ORDER >= *csk.value(),
|
||||||
"Secret key cannot exceed curve order"
|
"Secret key cannot exceed curve order"
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
let csk = nssa::PrivateKey::try_new({
|
||||||
|
let hash_value = hash_value
|
||||||
|
.first_chunk::<32>()
|
||||||
|
.expect("hash_value is 64 bytes, must be safe to get first 32");
|
||||||
|
|
||||||
|
let value_1 =
|
||||||
|
k256::Scalar::from_repr((*hash_value).into()).expect("Expect a valid k256 scalar");
|
||||||
|
let value_2 = k256::Scalar::from_repr((*self.csk.value()).into())
|
||||||
|
.expect("Expect a valid k256 scalar");
|
||||||
|
|
||||||
|
let sum = value_1.add(&value_2);
|
||||||
|
sum.to_bytes().into()
|
||||||
|
})
|
||||||
|
.expect("Expect a valid private key");
|
||||||
|
|
||||||
let ccc = *hash_value
|
let ccc = *hash_value
|
||||||
.last_chunk::<32>()
|
.last_chunk::<32>()
|
||||||
|
|||||||
@ -19,7 +19,7 @@ sha2.workspace = true
|
|||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
borsh.workspace = true
|
borsh.workspace = true
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
secp256k1 = "0.31.1"
|
k256.workspace = true
|
||||||
risc0-binfmt = "3.0.2"
|
risc0-binfmt = "3.0.2"
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ use crate::{
|
|||||||
NullifierSecretKey, SharedSecretKey,
|
NullifierSecretKey, SharedSecretKey,
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
encryption::Ciphertext,
|
encryption::Ciphertext,
|
||||||
program::{ProgramId, ProgramOutput, ValidityWindow},
|
program::{BlockValidityWindow, ProgramId, ProgramOutput, TimestampValidityWindow},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@ -36,7 +36,8 @@ pub struct PrivacyPreservingCircuitOutput {
|
|||||||
pub ciphertexts: Vec<Ciphertext>,
|
pub ciphertexts: Vec<Ciphertext>,
|
||||||
pub new_commitments: Vec<Commitment>,
|
pub new_commitments: Vec<Commitment>,
|
||||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||||
pub validity_window: ValidityWindow,
|
pub block_validity_window: BlockValidityWindow,
|
||||||
|
pub timestamp_validity_window: TimestampValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "host")]
|
#[cfg(feature = "host")]
|
||||||
@ -102,7 +103,8 @@ mod tests {
|
|||||||
),
|
),
|
||||||
[0xab; 32],
|
[0xab; 32],
|
||||||
)],
|
)],
|
||||||
validity_window: (Some(1), None).try_into().unwrap(),
|
block_validity_window: (1..).into(),
|
||||||
|
timestamp_validity_window: TimestampValidityWindow::new_unbounded(),
|
||||||
};
|
};
|
||||||
let bytes = output.to_bytes();
|
let bytes = output.to_bytes();
|
||||||
let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap();
|
let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap();
|
||||||
|
|||||||
@ -21,3 +21,7 @@ pub mod program;
|
|||||||
|
|
||||||
#[cfg(feature = "host")]
|
#[cfg(feature = "host")]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
|
pub type BlockId = u64;
|
||||||
|
/// Unix timestamp in milliseconds.
|
||||||
|
pub type Timestamp = u64;
|
||||||
|
|||||||
@ -5,7 +5,10 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
|||||||
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
|
use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::account::{Account, AccountId, AccountWithMetadata};
|
use crate::{
|
||||||
|
BlockId, Timestamp,
|
||||||
|
account::{Account, AccountId, AccountWithMetadata},
|
||||||
|
};
|
||||||
|
|
||||||
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
|
pub const DEFAULT_PROGRAM_ID: ProgramId = [0; 8];
|
||||||
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
|
pub const MAX_NUMBER_CHAINED_CALLS: usize = 10;
|
||||||
@ -22,7 +25,7 @@ pub struct ProgramInput<T> {
|
|||||||
/// Each program can derive up to `2^256` unique account IDs by choosing different
|
/// Each program can derive up to `2^256` unique account IDs by choosing different
|
||||||
/// seeds. PDAs allow programs to control namespaced account identifiers without
|
/// seeds. PDAs allow programs to control namespaced account identifiers without
|
||||||
/// collisions between programs.
|
/// collisions between programs.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PdaSeed([u8; 32]);
|
pub struct PdaSeed([u8; 32]);
|
||||||
|
|
||||||
impl PdaSeed {
|
impl PdaSeed {
|
||||||
@ -91,11 +94,26 @@ impl ChainedCall {
|
|||||||
/// A post state may optionally request that the executing program
|
/// A post state may optionally request that the executing program
|
||||||
/// becomes the owner of the account (a “claim”). This is used to signal
|
/// becomes the owner of the account (a “claim”). This is used to signal
|
||||||
/// that the program intends to take ownership of the account.
|
/// that the program intends to take ownership of the account.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[cfg_attr(any(feature = "host", test), derive(PartialEq, Eq))]
|
#[cfg_attr(any(feature = "host", test), derive(PartialEq, Eq))]
|
||||||
pub struct AccountPostState {
|
pub struct AccountPostState {
|
||||||
account: Account,
|
account: Account,
|
||||||
claim: bool,
|
claim: Option<Claim>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A claim request for an account, indicating that the executing program intends to take ownership
|
||||||
|
/// of the account.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum Claim {
|
||||||
|
/// The program requests ownership of the account which was authorized by the signer.
|
||||||
|
///
|
||||||
|
/// Note that it's possible to successfully execute program outputting [`AccountPostState`] with
|
||||||
|
/// `is_authorized == false` and `claim == Some(Claim::Authorized)`.
|
||||||
|
/// This will give no error if program had authorization in pre state and may be useful
|
||||||
|
/// if program decides to give up authorization for a chained call.
|
||||||
|
Authorized,
|
||||||
|
/// The program requests ownership of the account through a PDA.
|
||||||
|
Pda(PdaSeed),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountPostState {
|
impl AccountPostState {
|
||||||
@ -105,7 +123,7 @@ impl AccountPostState {
|
|||||||
pub const fn new(account: Account) -> Self {
|
pub const fn new(account: Account) -> Self {
|
||||||
Self {
|
Self {
|
||||||
account,
|
account,
|
||||||
claim: false,
|
claim: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,25 +131,27 @@ impl AccountPostState {
|
|||||||
/// This indicates that the executing program intends to claim the
|
/// This indicates that the executing program intends to claim the
|
||||||
/// account as its own and is allowed to mutate it.
|
/// account as its own and is allowed to mutate it.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new_claimed(account: Account) -> Self {
|
pub const fn new_claimed(account: Account, claim: Claim) -> Self {
|
||||||
Self {
|
Self {
|
||||||
account,
|
account,
|
||||||
claim: true,
|
claim: Some(claim),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a post state that requests ownership of the account
|
/// Creates a post state that requests ownership of the account
|
||||||
/// if the account's program owner is the default program ID.
|
/// if the account's program owner is the default program ID.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new_claimed_if_default(account: Account) -> Self {
|
pub fn new_claimed_if_default(account: Account, claim: Claim) -> Self {
|
||||||
let claim = account.program_owner == DEFAULT_PROGRAM_ID;
|
let is_default_owner = account.program_owner == DEFAULT_PROGRAM_ID;
|
||||||
Self { account, claim }
|
Self {
|
||||||
|
account,
|
||||||
|
claim: is_default_owner.then_some(claim),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if this post state requests that the account
|
/// Returns whether this post state requires a claim.
|
||||||
/// be claimed (owned) by the executing program.
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn requires_claim(&self) -> bool {
|
pub const fn required_claim(&self) -> Option<Claim> {
|
||||||
self.claim
|
self.claim
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +162,7 @@ impl AccountPostState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the underlying account.
|
/// Returns the underlying account.
|
||||||
|
#[must_use]
|
||||||
pub const fn account_mut(&mut self) -> &mut Account {
|
pub const fn account_mut(&mut self) -> &mut Account {
|
||||||
&mut self.account
|
&mut self.account
|
||||||
}
|
}
|
||||||
@ -153,20 +174,21 @@ impl AccountPostState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BlockId = u64;
|
pub type BlockValidityWindow = ValidityWindow<BlockId>;
|
||||||
|
pub type TimestampValidityWindow = ValidityWindow<Timestamp>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
any(feature = "host", test),
|
any(feature = "host", test),
|
||||||
derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)
|
derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)
|
||||||
)]
|
)]
|
||||||
pub struct ValidityWindow {
|
pub struct ValidityWindow<T> {
|
||||||
from: Option<BlockId>,
|
from: Option<T>,
|
||||||
to: Option<BlockId>,
|
to: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValidityWindow {
|
impl<T> ValidityWindow<T> {
|
||||||
/// Creates a window with no bounds, valid for every block ID.
|
/// Creates a window with no bounds.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new_unbounded() -> Self {
|
pub const fn new_unbounded() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -174,42 +196,42 @@ impl ValidityWindow {
|
|||||||
to: None,
|
to: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if `id` falls within the half-open range `[from, to)`.
|
impl<T: Copy + PartialOrd> ValidityWindow<T> {
|
||||||
/// A `None` bound on either side is treated as unbounded in that direction.
|
/// Valid for values in the range [from, to), where `from` is included and `to` is excluded.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_valid_for_block_id(&self, id: BlockId) -> bool {
|
pub fn is_valid_for(&self, value: T) -> bool {
|
||||||
self.from.is_none_or(|start| id >= start) && self.to.is_none_or(|end| id < end)
|
self.from.is_none_or(|start| value >= start) && self.to.is_none_or(|end| value < end)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `Err(InvalidWindow)` if both bounds are set and `from >= to`.
|
/// Returns `Err(InvalidWindow)` if both bounds are set and `from >= to`.
|
||||||
const fn check_window(&self) -> Result<(), InvalidWindow> {
|
fn check_window(&self) -> Result<(), InvalidWindow> {
|
||||||
if let (Some(from_id), Some(until_id)) = (self.from, self.to)
|
if let (Some(from), Some(to)) = (self.from, self.to)
|
||||||
&& from_id >= until_id
|
&& from >= to
|
||||||
{
|
{
|
||||||
Err(InvalidWindow)
|
return Err(InvalidWindow);
|
||||||
} else {
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Inclusive lower bound. `None` means the window starts at the beginning of the chain.
|
/// Inclusive lower bound. `None` means no lower bound.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn start(&self) -> Option<BlockId> {
|
pub const fn start(&self) -> Option<T> {
|
||||||
self.from
|
self.from
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exclusive upper bound. `None` means the window has no expiry.
|
/// Exclusive upper bound. `None` means no upper bound.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn end(&self) -> Option<BlockId> {
|
pub const fn end(&self) -> Option<T> {
|
||||||
self.to
|
self.to
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
|
impl<T: Copy + PartialOrd> TryFrom<(Option<T>, Option<T>)> for ValidityWindow<T> {
|
||||||
type Error = InvalidWindow;
|
type Error = InvalidWindow;
|
||||||
|
|
||||||
fn try_from(value: (Option<BlockId>, Option<BlockId>)) -> Result<Self, Self::Error> {
|
fn try_from(value: (Option<T>, Option<T>)) -> Result<Self, Self::Error> {
|
||||||
let this = Self {
|
let this = Self {
|
||||||
from: value.0,
|
from: value.0,
|
||||||
to: value.1,
|
to: value.1,
|
||||||
@ -219,16 +241,16 @@ impl TryFrom<(Option<BlockId>, Option<BlockId>)> for ValidityWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<std::ops::Range<BlockId>> for ValidityWindow {
|
impl<T: Copy + PartialOrd> TryFrom<std::ops::Range<T>> for ValidityWindow<T> {
|
||||||
type Error = InvalidWindow;
|
type Error = InvalidWindow;
|
||||||
|
|
||||||
fn try_from(value: std::ops::Range<BlockId>) -> Result<Self, Self::Error> {
|
fn try_from(value: std::ops::Range<T>) -> Result<Self, Self::Error> {
|
||||||
(Some(value.start), Some(value.end)).try_into()
|
(Some(value.start), Some(value.end)).try_into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::ops::RangeFrom<BlockId>> for ValidityWindow {
|
impl<T: Copy + PartialOrd> From<std::ops::RangeFrom<T>> for ValidityWindow<T> {
|
||||||
fn from(value: std::ops::RangeFrom<BlockId>) -> Self {
|
fn from(value: std::ops::RangeFrom<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
from: Some(value.start),
|
from: Some(value.start),
|
||||||
to: None,
|
to: None,
|
||||||
@ -236,8 +258,8 @@ impl From<std::ops::RangeFrom<BlockId>> for ValidityWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::ops::RangeTo<BlockId>> for ValidityWindow {
|
impl<T: Copy + PartialOrd> From<std::ops::RangeTo<T>> for ValidityWindow<T> {
|
||||||
fn from(value: std::ops::RangeTo<BlockId>) -> Self {
|
fn from(value: std::ops::RangeTo<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
from: None,
|
from: None,
|
||||||
to: Some(value.end),
|
to: Some(value.end),
|
||||||
@ -245,7 +267,7 @@ impl From<std::ops::RangeTo<BlockId>> for ValidityWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::ops::RangeFull> for ValidityWindow {
|
impl<T> From<std::ops::RangeFull> for ValidityWindow<T> {
|
||||||
fn from(_: std::ops::RangeFull) -> Self {
|
fn from(_: std::ops::RangeFull) -> Self {
|
||||||
Self::new_unbounded()
|
Self::new_unbounded()
|
||||||
}
|
}
|
||||||
@ -267,8 +289,10 @@ pub struct ProgramOutput {
|
|||||||
pub post_states: Vec<AccountPostState>,
|
pub post_states: Vec<AccountPostState>,
|
||||||
/// The list of chained calls to other programs.
|
/// The list of chained calls to other programs.
|
||||||
pub chained_calls: Vec<ChainedCall>,
|
pub chained_calls: Vec<ChainedCall>,
|
||||||
/// The window where the program output is valid.
|
/// The block ID window where the program output is valid.
|
||||||
pub validity_window: ValidityWindow,
|
pub block_validity_window: BlockValidityWindow,
|
||||||
|
/// The timestamp window where the program output is valid.
|
||||||
|
pub timestamp_validity_window: TimestampValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgramOutput {
|
impl ProgramOutput {
|
||||||
@ -282,7 +306,8 @@ impl ProgramOutput {
|
|||||||
pre_states,
|
pre_states,
|
||||||
post_states,
|
post_states,
|
||||||
chained_calls: Vec::new(),
|
chained_calls: Vec::new(),
|
||||||
validity_window: ValidityWindow::new_unbounded(),
|
block_validity_window: ValidityWindow::new_unbounded(),
|
||||||
|
timestamp_validity_window: ValidityWindow::new_unbounded(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,19 +320,52 @@ impl ProgramOutput {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the validity window from an infallible range conversion (`1..`, `..5`, `..`).
|
/// Sets the block ID validity window from an infallible range conversion (`1..`, `..5`, `..`).
|
||||||
pub fn with_validity_window<W: Into<ValidityWindow>>(mut self, window: W) -> Self {
|
pub fn with_block_validity_window<W: Into<BlockValidityWindow>>(mut self, window: W) -> Self {
|
||||||
self.validity_window = window.into();
|
self.block_validity_window = window.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the validity window from a fallible range conversion (`1..5`).
|
/// Sets the block ID validity window from a fallible range conversion (`1..5`).
|
||||||
/// Returns `Err` if the range is empty.
|
/// Returns `Err` if the range is empty.
|
||||||
pub fn try_with_validity_window<W: TryInto<ValidityWindow, Error = InvalidWindow>>(
|
pub fn try_with_block_validity_window<
|
||||||
|
W: TryInto<BlockValidityWindow, Error = InvalidWindow>,
|
||||||
|
>(
|
||||||
mut self,
|
mut self,
|
||||||
window: W,
|
window: W,
|
||||||
) -> Result<Self, InvalidWindow> {
|
) -> Result<Self, InvalidWindow> {
|
||||||
self.validity_window = window.try_into()?;
|
self.block_validity_window = window.try_into()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the timestamp validity window from an infallible range conversion.
|
||||||
|
pub fn with_timestamp_validity_window<W: Into<TimestampValidityWindow>>(
|
||||||
|
mut self,
|
||||||
|
window: W,
|
||||||
|
) -> Self {
|
||||||
|
self.timestamp_validity_window = window.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the timestamp validity window from a fallible range conversion.
|
||||||
|
/// Returns `Err` if the range is empty.
|
||||||
|
pub fn try_with_timestamp_validity_window<
|
||||||
|
W: TryInto<TimestampValidityWindow, Error = InvalidWindow>,
|
||||||
|
>(
|
||||||
|
mut self,
|
||||||
|
window: W,
|
||||||
|
) -> Result<Self, InvalidWindow> {
|
||||||
|
self.timestamp_validity_window = window.try_into()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn valid_from_timestamp(mut self, ts: Option<Timestamp>) -> Result<Self, InvalidWindow> {
|
||||||
|
self.timestamp_validity_window = (ts, self.timestamp_validity_window.end()).try_into()?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn valid_until_timestamp(mut self, ts: Option<Timestamp>) -> Result<Self, InvalidWindow> {
|
||||||
|
self.timestamp_validity_window = (self.timestamp_validity_window.start(), ts).try_into()?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -464,128 +522,131 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_unbounded_accepts_any_block() {
|
fn validity_window_unbounded_accepts_any_value() {
|
||||||
let w = ValidityWindow::new_unbounded();
|
let w: ValidityWindow<u64> = ValidityWindow::new_unbounded();
|
||||||
assert!(w.is_valid_for_block_id(0));
|
assert!(w.is_valid_for(0));
|
||||||
assert!(w.is_valid_for_block_id(u64::MAX));
|
assert!(w.is_valid_for(u64::MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_bounded_range_includes_from_excludes_to() {
|
fn validity_window_bounded_range_includes_from_excludes_to() {
|
||||||
let w: ValidityWindow = (Some(5), Some(10)).try_into().unwrap();
|
let w: ValidityWindow<u64> = (Some(5), Some(10)).try_into().unwrap();
|
||||||
assert!(!w.is_valid_for_block_id(4));
|
assert!(!w.is_valid_for(4));
|
||||||
assert!(w.is_valid_for_block_id(5));
|
assert!(w.is_valid_for(5));
|
||||||
assert!(w.is_valid_for_block_id(9));
|
assert!(w.is_valid_for(9));
|
||||||
assert!(!w.is_valid_for_block_id(10));
|
assert!(!w.is_valid_for(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_only_from_bound() {
|
fn validity_window_only_from_bound() {
|
||||||
let w: ValidityWindow = (Some(5), None).try_into().unwrap();
|
let w: ValidityWindow<u64> = (Some(5), None).try_into().unwrap();
|
||||||
assert!(!w.is_valid_for_block_id(4));
|
assert!(!w.is_valid_for(4));
|
||||||
assert!(w.is_valid_for_block_id(5));
|
assert!(w.is_valid_for(5));
|
||||||
assert!(w.is_valid_for_block_id(u64::MAX));
|
assert!(w.is_valid_for(u64::MAX));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_only_to_bound() {
|
fn validity_window_only_to_bound() {
|
||||||
let w: ValidityWindow = (None, Some(5)).try_into().unwrap();
|
let w: ValidityWindow<u64> = (None, Some(5)).try_into().unwrap();
|
||||||
assert!(w.is_valid_for_block_id(0));
|
assert!(w.is_valid_for(0));
|
||||||
assert!(w.is_valid_for_block_id(4));
|
assert!(w.is_valid_for(4));
|
||||||
assert!(!w.is_valid_for_block_id(5));
|
assert!(!w.is_valid_for(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_adjacent_bounds_are_invalid() {
|
fn validity_window_adjacent_bounds_are_invalid() {
|
||||||
// [5, 5) is an empty range — from == to
|
// [5, 5) is an empty range — from == to
|
||||||
assert!(ValidityWindow::try_from((Some(5), Some(5))).is_err());
|
assert!(ValidityWindow::<u64>::try_from((Some(5), Some(5))).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_inverted_bounds_are_invalid() {
|
fn validity_window_inverted_bounds_are_invalid() {
|
||||||
assert!(ValidityWindow::try_from((Some(10), Some(5))).is_err());
|
assert!(ValidityWindow::<u64>::try_from((Some(10), Some(5))).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_getters_match_construction() {
|
fn validity_window_getters_match_construction() {
|
||||||
let w: ValidityWindow = (Some(3), Some(7)).try_into().unwrap();
|
let w: ValidityWindow<u64> = (Some(3), Some(7)).try_into().unwrap();
|
||||||
assert_eq!(w.start(), Some(3));
|
assert_eq!(w.start(), Some(3));
|
||||||
assert_eq!(w.end(), Some(7));
|
assert_eq!(w.end(), Some(7));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_getters_for_unbounded() {
|
fn validity_window_getters_for_unbounded() {
|
||||||
let w = ValidityWindow::new_unbounded();
|
let w: ValidityWindow<u64> = ValidityWindow::new_unbounded();
|
||||||
assert_eq!(w.start(), None);
|
assert_eq!(w.start(), None);
|
||||||
assert_eq!(w.end(), None);
|
assert_eq!(w.end(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_from_range() {
|
fn validity_window_from_range() {
|
||||||
let w = ValidityWindow::try_from(5_u64..10).unwrap();
|
let w: ValidityWindow<u64> = ValidityWindow::try_from(5_u64..10).unwrap();
|
||||||
assert_eq!(w.start(), Some(5));
|
assert_eq!(w.start(), Some(5));
|
||||||
assert_eq!(w.end(), Some(10));
|
assert_eq!(w.end(), Some(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_from_range_empty_is_invalid() {
|
fn validity_window_from_range_empty_is_invalid() {
|
||||||
assert!(ValidityWindow::try_from(5_u64..5).is_err());
|
assert!(ValidityWindow::<u64>::try_from(5_u64..5).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_from_range_inverted_is_invalid() {
|
fn validity_window_from_range_inverted_is_invalid() {
|
||||||
let from = 10_u64;
|
let from = 10_u64;
|
||||||
let to = 5_u64;
|
let to = 5_u64;
|
||||||
assert!(ValidityWindow::try_from(from..to).is_err());
|
assert!(ValidityWindow::<u64>::try_from(from..to).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_from_range_from() {
|
fn validity_window_from_range_from() {
|
||||||
let w: ValidityWindow = (5_u64..).into();
|
let w: ValidityWindow<u64> = (5_u64..).into();
|
||||||
assert_eq!(w.start(), Some(5));
|
assert_eq!(w.start(), Some(5));
|
||||||
assert_eq!(w.end(), None);
|
assert_eq!(w.end(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_from_range_to() {
|
fn validity_window_from_range_to() {
|
||||||
let w: ValidityWindow = (..10_u64).into();
|
let w: ValidityWindow<u64> = (..10_u64).into();
|
||||||
assert_eq!(w.start(), None);
|
assert_eq!(w.start(), None);
|
||||||
assert_eq!(w.end(), Some(10));
|
assert_eq!(w.end(), Some(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validity_window_from_range_full() {
|
fn validity_window_from_range_full() {
|
||||||
let w: ValidityWindow = (..).into();
|
let w: ValidityWindow<u64> = (..).into();
|
||||||
assert_eq!(w.start(), None);
|
assert_eq!(w.start(), None);
|
||||||
assert_eq!(w.end(), None);
|
assert_eq!(w.end(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn program_output_try_with_validity_window_range() {
|
fn program_output_try_with_block_validity_window_range() {
|
||||||
let output = ProgramOutput::new(vec![], vec![], vec![])
|
let output = ProgramOutput::new(vec![], vec![], vec![])
|
||||||
.try_with_validity_window(10_u64..100)
|
.try_with_block_validity_window(10_u64..100)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(output.validity_window.start(), Some(10));
|
assert_eq!(output.block_validity_window.start(), Some(10));
|
||||||
assert_eq!(output.validity_window.end(), Some(100));
|
assert_eq!(output.block_validity_window.end(), Some(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn program_output_with_validity_window_range_from() {
|
fn program_output_with_block_validity_window_range_from() {
|
||||||
let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(10_u64..);
|
let output =
|
||||||
assert_eq!(output.validity_window.start(), Some(10));
|
ProgramOutput::new(vec![], vec![], vec![]).with_block_validity_window(10_u64..);
|
||||||
assert_eq!(output.validity_window.end(), None);
|
assert_eq!(output.block_validity_window.start(), Some(10));
|
||||||
|
assert_eq!(output.block_validity_window.end(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn program_output_with_validity_window_range_to() {
|
fn program_output_with_block_validity_window_range_to() {
|
||||||
let output = ProgramOutput::new(vec![], vec![], vec![]).with_validity_window(..100_u64);
|
let output =
|
||||||
assert_eq!(output.validity_window.start(), None);
|
ProgramOutput::new(vec![], vec![], vec![]).with_block_validity_window(..100_u64);
|
||||||
assert_eq!(output.validity_window.end(), Some(100));
|
assert_eq!(output.block_validity_window.start(), None);
|
||||||
|
assert_eq!(output.block_validity_window.end(), Some(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn program_output_try_with_validity_window_empty_range_fails() {
|
fn program_output_try_with_block_validity_window_empty_range_fails() {
|
||||||
let result = ProgramOutput::new(vec![], vec![], vec![]).try_with_validity_window(5_u64..5);
|
let result =
|
||||||
|
ProgramOutput::new(vec![], vec![], vec![]).try_with_block_validity_window(5_u64..5);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,10 +659,10 @@ mod tests {
|
|||||||
nonce: 10_u128.into(),
|
nonce: 10_u128.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let account_post_state = AccountPostState::new_claimed(account.clone());
|
let account_post_state = AccountPostState::new_claimed(account.clone(), Claim::Authorized);
|
||||||
|
|
||||||
assert_eq!(account, account_post_state.account);
|
assert_eq!(account, account_post_state.account);
|
||||||
assert!(account_post_state.requires_claim());
|
assert_eq!(account_post_state.required_claim(), Some(Claim::Authorized));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -616,7 +677,7 @@ mod tests {
|
|||||||
let account_post_state = AccountPostState::new(account.clone());
|
let account_post_state = AccountPostState::new(account.clone());
|
||||||
|
|
||||||
assert_eq!(account, account_post_state.account);
|
assert_eq!(account, account_post_state.account);
|
||||||
assert!(!account_post_state.requires_claim());
|
assert!(account_post_state.required_claim().is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -29,7 +29,10 @@ pub enum NssaError {
|
|||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
|
|
||||||
#[error("Invalid Public Key")]
|
#[error("Invalid Public Key")]
|
||||||
InvalidPublicKey(#[source] secp256k1::Error),
|
InvalidPublicKey(#[source] k256::schnorr::Error),
|
||||||
|
|
||||||
|
#[error("Invalid hex for public key")]
|
||||||
|
InvalidHexPublicKey(hex::FromHexError),
|
||||||
|
|
||||||
#[error("Risc0 error: {0}")]
|
#[error("Risc0 error: {0}")]
|
||||||
ProgramWriteInputFailed(String),
|
ProgramWriteInputFailed(String),
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use nssa_core::{
|
|||||||
Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput,
|
Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput,
|
||||||
account::{Account, Nonce},
|
account::{Account, Nonce},
|
||||||
encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey},
|
encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey},
|
||||||
program::ValidityWindow,
|
program::{BlockValidityWindow, TimestampValidityWindow},
|
||||||
};
|
};
|
||||||
use sha2::{Digest as _, Sha256};
|
use sha2::{Digest as _, Sha256};
|
||||||
|
|
||||||
@ -53,7 +53,8 @@ pub struct Message {
|
|||||||
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
||||||
pub new_commitments: Vec<Commitment>,
|
pub new_commitments: Vec<Commitment>,
|
||||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||||
pub validity_window: ValidityWindow,
|
pub block_validity_window: BlockValidityWindow,
|
||||||
|
pub timestamp_validity_window: TimestampValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Message {
|
impl std::fmt::Debug for Message {
|
||||||
@ -79,7 +80,8 @@ impl std::fmt::Debug for Message {
|
|||||||
)
|
)
|
||||||
.field("new_commitments", &self.new_commitments)
|
.field("new_commitments", &self.new_commitments)
|
||||||
.field("new_nullifiers", &nullifiers)
|
.field("new_nullifiers", &nullifiers)
|
||||||
.field("validity_window", &self.validity_window)
|
.field("block_validity_window", &self.block_validity_window)
|
||||||
|
.field("timestamp_validity_window", &self.timestamp_validity_window)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,7 +114,8 @@ impl Message {
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments: output.new_commitments,
|
new_commitments: output.new_commitments,
|
||||||
new_nullifiers: output.new_nullifiers,
|
new_nullifiers: output.new_nullifiers,
|
||||||
validity_window: output.validity_window,
|
block_validity_window: output.block_validity_window,
|
||||||
|
timestamp_validity_window: output.timestamp_validity_window,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,6 +126,7 @@ pub mod tests {
|
|||||||
Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, SharedSecretKey,
|
Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, SharedSecretKey,
|
||||||
account::Account,
|
account::Account,
|
||||||
encryption::{EphemeralPublicKey, ViewingPublicKey},
|
encryption::{EphemeralPublicKey, ViewingPublicKey},
|
||||||
|
program::{BlockValidityWindow, TimestampValidityWindow},
|
||||||
};
|
};
|
||||||
use sha2::{Digest as _, Sha256};
|
use sha2::{Digest as _, Sha256};
|
||||||
|
|
||||||
@ -165,7 +169,8 @@ pub mod tests {
|
|||||||
encrypted_private_post_states,
|
encrypted_private_post_states,
|
||||||
new_commitments,
|
new_commitments,
|
||||||
new_nullifiers,
|
new_nullifiers,
|
||||||
validity_window: (None, None).try_into().unwrap(),
|
block_validity_window: BlockValidityWindow::new_unbounded(),
|
||||||
|
timestamp_validity_window: TimestampValidityWindow::new_unbounded(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,17 +5,14 @@ use std::{
|
|||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput,
|
BlockId, PrivacyPreservingCircuitOutput, Timestamp,
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{BlockId, ValidityWindow},
|
|
||||||
};
|
};
|
||||||
use sha2::{Digest as _, digest::FixedOutput as _};
|
use sha2::{Digest as _, digest::FixedOutput as _};
|
||||||
|
|
||||||
use super::{message::Message, witness_set::WitnessSet};
|
use super::{message::Message, witness_set::WitnessSet};
|
||||||
use crate::{
|
use crate::{
|
||||||
AccountId, V03State,
|
AccountId, V03State, error::NssaError, privacy_preserving_transaction::circuit::Proof,
|
||||||
error::NssaError,
|
|
||||||
privacy_preserving_transaction::{circuit::Proof, message::EncryptedAccountData},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||||
@ -37,6 +34,7 @@ impl PrivacyPreservingTransaction {
|
|||||||
&self,
|
&self,
|
||||||
state: &V03State,
|
state: &V03State,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
|
timestamp: Timestamp,
|
||||||
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
||||||
let message = &self.message;
|
let message = &self.message;
|
||||||
let witness_set = &self.witness_set;
|
let witness_set = &self.witness_set;
|
||||||
@ -94,7 +92,9 @@ impl PrivacyPreservingTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify validity window
|
// Verify validity window
|
||||||
if !message.validity_window.is_valid_for_block_id(block_id) {
|
if !message.block_validity_window.is_valid_for(block_id)
|
||||||
|
|| !message.timestamp_validity_window.is_valid_for(timestamp)
|
||||||
|
{
|
||||||
return Err(NssaError::OutOfValidityWindow);
|
return Err(NssaError::OutOfValidityWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,11 +115,7 @@ impl PrivacyPreservingTransaction {
|
|||||||
check_privacy_preserving_circuit_proof_is_valid(
|
check_privacy_preserving_circuit_proof_is_valid(
|
||||||
&witness_set.proof,
|
&witness_set.proof,
|
||||||
&public_pre_states,
|
&public_pre_states,
|
||||||
&message.public_post_states,
|
message,
|
||||||
&message.encrypted_private_post_states,
|
|
||||||
&message.new_commitments,
|
|
||||||
&message.new_nullifiers,
|
|
||||||
&message.validity_window,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// 5. Commitment freshness
|
// 5. Commitment freshness
|
||||||
@ -177,23 +173,21 @@ impl PrivacyPreservingTransaction {
|
|||||||
fn check_privacy_preserving_circuit_proof_is_valid(
|
fn check_privacy_preserving_circuit_proof_is_valid(
|
||||||
proof: &Proof,
|
proof: &Proof,
|
||||||
public_pre_states: &[AccountWithMetadata],
|
public_pre_states: &[AccountWithMetadata],
|
||||||
public_post_states: &[Account],
|
message: &Message,
|
||||||
encrypted_private_post_states: &[EncryptedAccountData],
|
|
||||||
new_commitments: &[Commitment],
|
|
||||||
new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
|
|
||||||
validity_window: &ValidityWindow,
|
|
||||||
) -> Result<(), NssaError> {
|
) -> Result<(), NssaError> {
|
||||||
let output = PrivacyPreservingCircuitOutput {
|
let output = PrivacyPreservingCircuitOutput {
|
||||||
public_pre_states: public_pre_states.to_vec(),
|
public_pre_states: public_pre_states.to_vec(),
|
||||||
public_post_states: public_post_states.to_vec(),
|
public_post_states: message.public_post_states.clone(),
|
||||||
ciphertexts: encrypted_private_post_states
|
ciphertexts: message
|
||||||
|
.encrypted_private_post_states
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|value| value.ciphertext)
|
.map(|value| value.ciphertext)
|
||||||
.collect(),
|
.collect(),
|
||||||
new_commitments: new_commitments.to_vec(),
|
new_commitments: message.new_commitments.clone(),
|
||||||
new_nullifiers: new_nullifiers.to_vec(),
|
new_nullifiers: message.new_nullifiers.clone(),
|
||||||
validity_window: validity_window.to_owned(),
|
block_validity_window: message.block_validity_window,
|
||||||
|
timestamp_validity_window: message.timestamp_validity_window,
|
||||||
};
|
};
|
||||||
proof
|
proof
|
||||||
.is_valid_for(&output)
|
.is_valid_for(&output)
|
||||||
|
|||||||
@ -3,8 +3,9 @@ use std::collections::{HashMap, HashSet, VecDeque};
|
|||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
|
BlockId, Timestamp,
|
||||||
account::{Account, AccountId, AccountWithMetadata},
|
account::{Account, AccountId, AccountWithMetadata},
|
||||||
program::{BlockId, ChainedCall, DEFAULT_PROGRAM_ID, validate_execution},
|
program::{ChainedCall, Claim, DEFAULT_PROGRAM_ID, validate_execution},
|
||||||
};
|
};
|
||||||
use sha2::{Digest as _, digest::FixedOutput as _};
|
use sha2::{Digest as _, digest::FixedOutput as _};
|
||||||
|
|
||||||
@ -71,6 +72,7 @@ impl PublicTransaction {
|
|||||||
&self,
|
&self,
|
||||||
state: &V03State,
|
state: &V03State,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
|
timestamp: Timestamp,
|
||||||
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
) -> Result<HashMap<AccountId, Account>, NssaError> {
|
||||||
let message = self.message();
|
let message = self.message();
|
||||||
let witness_set = self.witness_set();
|
let witness_set = self.witness_set();
|
||||||
@ -157,6 +159,10 @@ impl PublicTransaction {
|
|||||||
&chained_call.pda_seeds,
|
&chained_call.pda_seeds,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let is_authorized = |account_id: &AccountId| {
|
||||||
|
signer_account_ids.contains(account_id) || authorized_pdas.contains(account_id)
|
||||||
|
};
|
||||||
|
|
||||||
for pre in &program_output.pre_states {
|
for pre in &program_output.pre_states {
|
||||||
let account_id = pre.account_id;
|
let account_id = pre.account_id;
|
||||||
// Check that the program output pre_states coincide with the values in the public
|
// Check that the program output pre_states coincide with the values in the public
|
||||||
@ -172,10 +178,8 @@ impl PublicTransaction {
|
|||||||
|
|
||||||
// Check that authorization flags are consistent with the provided ones or
|
// Check that authorization flags are consistent with the provided ones or
|
||||||
// authorized by program through the PDA mechanism
|
// authorized by program through the PDA mechanism
|
||||||
let is_authorized = signer_account_ids.contains(&account_id)
|
|
||||||
|| authorized_pdas.contains(&account_id);
|
|
||||||
ensure!(
|
ensure!(
|
||||||
pre.is_authorized == is_authorized,
|
pre.is_authorized == is_authorized(&account_id),
|
||||||
NssaError::InvalidProgramBehavior
|
NssaError::InvalidProgramBehavior
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -193,23 +197,42 @@ impl PublicTransaction {
|
|||||||
|
|
||||||
// Verify validity window
|
// Verify validity window
|
||||||
ensure!(
|
ensure!(
|
||||||
program_output
|
program_output.block_validity_window.is_valid_for(block_id)
|
||||||
.validity_window
|
&& program_output
|
||||||
.is_valid_for_block_id(block_id),
|
.timestamp_validity_window
|
||||||
|
.is_valid_for(timestamp),
|
||||||
NssaError::OutOfValidityWindow
|
NssaError::OutOfValidityWindow
|
||||||
);
|
);
|
||||||
|
|
||||||
for post in program_output
|
for (i, post) in program_output.post_states.iter_mut().enumerate() {
|
||||||
.post_states
|
let Some(claim) = post.required_claim() else {
|
||||||
.iter_mut()
|
continue;
|
||||||
.filter(|post| post.requires_claim())
|
};
|
||||||
{
|
|
||||||
// The invoked program can only claim accounts with default program id.
|
// The invoked program can only claim accounts with default program id.
|
||||||
if post.account().program_owner == DEFAULT_PROGRAM_ID {
|
ensure!(
|
||||||
post.account_mut().program_owner = chained_call.program_id;
|
post.account().program_owner == DEFAULT_PROGRAM_ID,
|
||||||
} else {
|
NssaError::InvalidProgramBehavior
|
||||||
return Err(NssaError::InvalidProgramBehavior);
|
);
|
||||||
|
|
||||||
|
let account_id = program_output.pre_states[i].account_id;
|
||||||
|
|
||||||
|
match claim {
|
||||||
|
Claim::Authorized => {
|
||||||
|
// The program can only claim accounts that were authorized by the signer.
|
||||||
|
ensure!(
|
||||||
|
is_authorized(&account_id),
|
||||||
|
NssaError::InvalidProgramBehavior
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Claim::Pda(seed) => {
|
||||||
|
// The program can only claim accounts that correspond to the PDAs it is
|
||||||
|
// authorized to claim.
|
||||||
|
let pda = AccountId::from((&chained_call.program_id, &seed));
|
||||||
|
ensure!(account_id == pda, NssaError::InvalidProgramBehavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post.account_mut().program_owner = chained_call.program_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the state diff
|
// Update the state diff
|
||||||
@ -368,7 +391,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +411,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +432,7 @@ pub mod tests {
|
|||||||
let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
|
witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,7 +452,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,7 +468,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = tx.validate_and_produce_public_state_diff(&state, 1);
|
let result = tx.validate_and_produce_public_state_diff(&state, 1, 0);
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,21 +49,28 @@ impl Signature {
|
|||||||
aux_random: [u8; 32],
|
aux_random: [u8; 32],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let value = {
|
let value = {
|
||||||
let secp = secp256k1::Secp256k1::new();
|
let signing_key = k256::schnorr::SigningKey::from_bytes(key.value())
|
||||||
let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap();
|
.expect("Expect valid signing key");
|
||||||
let keypair = secp256k1::Keypair::from_secret_key(&secp, &secret_key);
|
signing_key
|
||||||
let signature = secp.sign_schnorr_with_aux_rand(message, &keypair, &aux_random);
|
.sign_raw(message, &aux_random)
|
||||||
signature.to_byte_array()
|
.expect("Expect to produce a valid signature")
|
||||||
|
.to_bytes()
|
||||||
};
|
};
|
||||||
|
|
||||||
Self { value }
|
Self { value }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool {
|
pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool {
|
||||||
let pk = secp256k1::XOnlyPublicKey::from_byte_array(*public_key.value()).unwrap();
|
let Ok(pk) = k256::schnorr::VerifyingKey::from_bytes(public_key.value()) else {
|
||||||
let secp = secp256k1::Secp256k1::new();
|
return false;
|
||||||
let sig = secp256k1::schnorr::Signature::from_byte_array(self.value);
|
};
|
||||||
secp.verify_schnorr(&sig, bytes, &pk).is_ok()
|
|
||||||
|
let Ok(sig) = k256::schnorr::Signature::try_from(self.value.as_slice()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
pk.verify_raw(bytes, &sig).is_ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ impl PrivateKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_key(value: [u8; 32]) -> bool {
|
fn is_valid_key(value: [u8; 32]) -> bool {
|
||||||
secp256k1::SecretKey::from_byte_array(value).is_ok()
|
k256::SecretKey::from_bytes(&value.into()).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
|
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
|
use k256::elliptic_curve::sec1::ToEncodedPoint as _;
|
||||||
use nssa_core::account::AccountId;
|
use nssa_core::account::AccountId;
|
||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
use sha2::{Digest as _, Sha256};
|
use sha2::{Digest as _, Sha256};
|
||||||
@ -27,8 +28,7 @@ impl FromStr for PublicKey {
|
|||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let mut bytes = [0_u8; 32];
|
let mut bytes = [0_u8; 32];
|
||||||
hex::decode_to_slice(s, &mut bytes)
|
hex::decode_to_slice(s, &mut bytes).map_err(NssaError::InvalidHexPublicKey)?;
|
||||||
.map_err(|_err| NssaError::InvalidPublicKey(secp256k1::Error::InvalidPublicKey))?;
|
|
||||||
Self::try_new(bytes)
|
Self::try_new(bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,19 +46,24 @@ impl PublicKey {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new_from_private_key(key: &PrivateKey) -> Self {
|
pub fn new_from_private_key(key: &PrivateKey) -> Self {
|
||||||
let value = {
|
let value = {
|
||||||
let secret_key = secp256k1::SecretKey::from_byte_array(*key.value()).unwrap();
|
let secret_key = k256::SecretKey::from_bytes(&(*key.value()).into())
|
||||||
let public_key =
|
.expect("Expect a valid private key");
|
||||||
secp256k1::PublicKey::from_secret_key(&secp256k1::Secp256k1::new(), &secret_key);
|
|
||||||
let (x_only, _) = public_key.x_only_public_key();
|
let encoded = secret_key.public_key().to_encoded_point(false);
|
||||||
x_only.serialize()
|
let x_only = encoded
|
||||||
|
.x()
|
||||||
|
.expect("Expect k256 point to have a x-coordinate");
|
||||||
|
|
||||||
|
*x_only.first_chunk().expect("x_only is exactly 32 bytes")
|
||||||
};
|
};
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
|
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaError> {
|
||||||
// Check point is valid
|
// Check point is a valid x-only public key
|
||||||
let _ = secp256k1::XOnlyPublicKey::from_byte_array(value)
|
let _ =
|
||||||
.map_err(NssaError::InvalidPublicKey)?;
|
k256::schnorr::VerifyingKey::from_bytes(&value).map_err(NssaError::InvalidPublicKey)?;
|
||||||
|
|
||||||
Ok(Self(value))
|
Ok(Self(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,10 @@ use std::collections::{BTreeSet, HashMap, HashSet};
|
|||||||
|
|
||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier,
|
||||||
|
Timestamp,
|
||||||
account::{Account, AccountId, Nonce},
|
account::{Account, AccountId, Nonce},
|
||||||
program::{BlockId, ProgramId},
|
program::ProgramId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -159,8 +160,9 @@ impl V03State {
|
|||||||
&mut self,
|
&mut self,
|
||||||
tx: &PublicTransaction,
|
tx: &PublicTransaction,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
|
timestamp: Timestamp,
|
||||||
) -> Result<(), NssaError> {
|
) -> Result<(), NssaError> {
|
||||||
let state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?;
|
let state_diff = tx.validate_and_produce_public_state_diff(self, block_id, timestamp)?;
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::iter_over_hash_type,
|
clippy::iter_over_hash_type,
|
||||||
@ -184,9 +186,11 @@ impl V03State {
|
|||||||
&mut self,
|
&mut self,
|
||||||
tx: &PrivacyPreservingTransaction,
|
tx: &PrivacyPreservingTransaction,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
|
timestamp: Timestamp,
|
||||||
) -> Result<(), NssaError> {
|
) -> Result<(), NssaError> {
|
||||||
// 1. Verify the transaction satisfies acceptance criteria
|
// 1. Verify the transaction satisfies acceptance criteria
|
||||||
let public_state_diff = tx.validate_and_produce_public_state_diff(self, block_id)?;
|
let public_state_diff =
|
||||||
|
tx.validate_and_produce_public_state_diff(self, block_id, timestamp)?;
|
||||||
|
|
||||||
let message = tx.message();
|
let message = tx.message();
|
||||||
|
|
||||||
@ -338,10 +342,11 @@ pub mod tests {
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
BlockId, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||||
|
Timestamp,
|
||||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||||
encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey},
|
encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey},
|
||||||
program::{BlockId, PdaSeed, ProgramId, ValidityWindow},
|
program::{BlockValidityWindow, PdaSeed, ProgramId, TimestampValidityWindow},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -456,16 +461,19 @@ pub mod tests {
|
|||||||
fn transfer_transaction(
|
fn transfer_transaction(
|
||||||
from: AccountId,
|
from: AccountId,
|
||||||
from_key: &PrivateKey,
|
from_key: &PrivateKey,
|
||||||
nonce: u128,
|
from_nonce: u128,
|
||||||
to: AccountId,
|
to: AccountId,
|
||||||
|
to_key: &PrivateKey,
|
||||||
|
to_nonce: u128,
|
||||||
balance: u128,
|
balance: u128,
|
||||||
) -> PublicTransaction {
|
) -> PublicTransaction {
|
||||||
let account_ids = vec![from, to];
|
let account_ids = vec![from, to];
|
||||||
let nonces = vec![Nonce(nonce)];
|
let nonces = vec![Nonce(from_nonce), Nonce(to_nonce)];
|
||||||
let program_id = Program::authenticated_transfer_program().id();
|
let program_id = Program::authenticated_transfer_program().id();
|
||||||
let message =
|
let message =
|
||||||
public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap();
|
public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]);
|
let witness_set =
|
||||||
|
public_transaction::WitnessSet::for_message(&message, &[from_key, to_key]);
|
||||||
PublicTransaction::new(message, witness_set)
|
PublicTransaction::new(message, witness_set)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,17 +575,18 @@ pub mod tests {
|
|||||||
let initial_data = [(account_id, 100)];
|
let initial_data = [(account_id, 100)];
|
||||||
let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]);
|
let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]);
|
||||||
let from = account_id;
|
let from = account_id;
|
||||||
let to = AccountId::new([2; 32]);
|
let to_key = PrivateKey::try_new([2; 32]).unwrap();
|
||||||
|
let to = AccountId::from(&PublicKey::new_from_private_key(&to_key));
|
||||||
assert_eq!(state.get_account_by_id(to), Account::default());
|
assert_eq!(state.get_account_by_id(to), Account::default());
|
||||||
let balance_to_move = 5;
|
let balance_to_move = 5;
|
||||||
|
|
||||||
let tx = transfer_transaction(from, &key, 0, to, balance_to_move);
|
let tx = transfer_transaction(from, &key, 0, to, &to_key, 0, balance_to_move);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
assert_eq!(state.get_account_by_id(from).balance, 95);
|
assert_eq!(state.get_account_by_id(from).balance, 95);
|
||||||
assert_eq!(state.get_account_by_id(to).balance, 5);
|
assert_eq!(state.get_account_by_id(to).balance, 5);
|
||||||
assert_eq!(state.get_account_by_id(from).nonce, Nonce(1));
|
assert_eq!(state.get_account_by_id(from).nonce, Nonce(1));
|
||||||
assert_eq!(state.get_account_by_id(to).nonce, Nonce(0));
|
assert_eq!(state.get_account_by_id(to).nonce, Nonce(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -588,12 +597,13 @@ pub mod tests {
|
|||||||
let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]);
|
let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]);
|
||||||
let from = account_id;
|
let from = account_id;
|
||||||
let from_key = key;
|
let from_key = key;
|
||||||
let to = AccountId::new([2; 32]);
|
let to_key = PrivateKey::try_new([2; 32]).unwrap();
|
||||||
|
let to = AccountId::from(&PublicKey::new_from_private_key(&to_key));
|
||||||
let balance_to_move = 101;
|
let balance_to_move = 101;
|
||||||
assert!(state.get_account_by_id(from).balance < balance_to_move);
|
assert!(state.get_account_by_id(from).balance < balance_to_move);
|
||||||
|
|
||||||
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
let tx = transfer_transaction(from, &from_key, 0, to, &to_key, 0, balance_to_move);
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
|
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
|
||||||
assert_eq!(state.get_account_by_id(from).balance, 100);
|
assert_eq!(state.get_account_by_id(from).balance, 100);
|
||||||
@ -613,16 +623,17 @@ pub mod tests {
|
|||||||
let from = account_id2;
|
let from = account_id2;
|
||||||
let from_key = key2;
|
let from_key = key2;
|
||||||
let to = account_id1;
|
let to = account_id1;
|
||||||
|
let to_key = key1;
|
||||||
assert_ne!(state.get_account_by_id(to), Account::default());
|
assert_ne!(state.get_account_by_id(to), Account::default());
|
||||||
let balance_to_move = 8;
|
let balance_to_move = 8;
|
||||||
|
|
||||||
let tx = transfer_transaction(from, &from_key, 0, to, balance_to_move);
|
let tx = transfer_transaction(from, &from_key, 0, to, &to_key, 0, balance_to_move);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
assert_eq!(state.get_account_by_id(from).balance, 192);
|
assert_eq!(state.get_account_by_id(from).balance, 192);
|
||||||
assert_eq!(state.get_account_by_id(to).balance, 108);
|
assert_eq!(state.get_account_by_id(to).balance, 108);
|
||||||
assert_eq!(state.get_account_by_id(from).nonce, Nonce(1));
|
assert_eq!(state.get_account_by_id(from).nonce, Nonce(1));
|
||||||
assert_eq!(state.get_account_by_id(to).nonce, Nonce(0));
|
assert_eq!(state.get_account_by_id(to).nonce, Nonce(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -633,21 +644,38 @@ pub mod tests {
|
|||||||
let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2));
|
let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2));
|
||||||
let initial_data = [(account_id1, 100)];
|
let initial_data = [(account_id1, 100)];
|
||||||
let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]);
|
let mut state = V03State::new_with_genesis_accounts(&initial_data, &[]);
|
||||||
let account_id3 = AccountId::new([3; 32]);
|
let key3 = PrivateKey::try_new([3; 32]).unwrap();
|
||||||
|
let account_id3 = AccountId::from(&PublicKey::new_from_private_key(&key3));
|
||||||
let balance_to_move = 5;
|
let balance_to_move = 5;
|
||||||
|
|
||||||
let tx = transfer_transaction(account_id1, &key1, 0, account_id2, balance_to_move);
|
let tx = transfer_transaction(
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
account_id1,
|
||||||
|
&key1,
|
||||||
|
0,
|
||||||
|
account_id2,
|
||||||
|
&key2,
|
||||||
|
0,
|
||||||
|
balance_to_move,
|
||||||
|
);
|
||||||
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
let balance_to_move = 3;
|
let balance_to_move = 3;
|
||||||
let tx = transfer_transaction(account_id2, &key2, 0, account_id3, balance_to_move);
|
let tx = transfer_transaction(
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
account_id2,
|
||||||
|
&key2,
|
||||||
|
1,
|
||||||
|
account_id3,
|
||||||
|
&key3,
|
||||||
|
0,
|
||||||
|
balance_to_move,
|
||||||
|
);
|
||||||
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
|
assert_eq!(state.get_account_by_id(account_id1).balance, 95);
|
||||||
assert_eq!(state.get_account_by_id(account_id2).balance, 2);
|
assert_eq!(state.get_account_by_id(account_id2).balance, 2);
|
||||||
assert_eq!(state.get_account_by_id(account_id3).balance, 3);
|
assert_eq!(state.get_account_by_id(account_id3).balance, 3);
|
||||||
assert_eq!(state.get_account_by_id(account_id1).nonce, Nonce(1));
|
assert_eq!(state.get_account_by_id(account_id1).nonce, Nonce(1));
|
||||||
assert_eq!(state.get_account_by_id(account_id2).nonce, Nonce(1));
|
assert_eq!(state.get_account_by_id(account_id2).nonce, Nonce(2));
|
||||||
assert_eq!(state.get_account_by_id(account_id3).nonce, Nonce(0));
|
assert_eq!(state.get_account_by_id(account_id3).nonce, Nonce(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -662,7 +690,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -679,7 +707,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -696,7 +724,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -720,7 +748,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -744,7 +772,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -768,7 +796,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -792,7 +820,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -820,7 +848,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -845,7 +873,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -863,7 +891,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -892,7 +920,7 @@ pub mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -1085,7 +1113,7 @@ pub mod tests {
|
|||||||
assert!(!state.private_state.0.contains(&expected_new_commitment));
|
assert!(!state.private_state.0.contains(&expected_new_commitment));
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx, 1)
|
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sender_post = state.get_account_by_id(sender_keys.account_id());
|
let sender_post = state.get_account_by_id(sender_keys.account_id());
|
||||||
@ -1155,7 +1183,7 @@ pub mod tests {
|
|||||||
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx, 1)
|
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(state.public_state, previous_public_state);
|
assert_eq!(state.public_state, previous_public_state);
|
||||||
@ -1219,7 +1247,7 @@ pub mod tests {
|
|||||||
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
assert!(!state.private_state.1.contains(&expected_new_nullifier));
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx, 1)
|
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let recipient_post = state.get_account_by_id(recipient_keys.account_id());
|
let recipient_post = state.get_account_by_id(recipient_keys.account_id());
|
||||||
@ -2147,7 +2175,7 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx, 1)
|
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sender_private_account = Account {
|
let sender_private_account = Account {
|
||||||
@ -2165,7 +2193,7 @@ pub mod tests {
|
|||||||
&state,
|
&state,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = state.transition_from_privacy_preserving_transaction(&tx, 1);
|
let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
|
||||||
let NssaError::InvalidInput(error_message) = result.err().unwrap() else {
|
let NssaError::InvalidInput(error_message) = result.err().unwrap() else {
|
||||||
@ -2212,15 +2240,14 @@ pub mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn claiming_mechanism() {
|
fn claiming_mechanism() {
|
||||||
let program = Program::authenticated_transfer_program();
|
let program = Program::authenticated_transfer_program();
|
||||||
let key = PrivateKey::try_new([1; 32]).unwrap();
|
let from_key = PrivateKey::try_new([1; 32]).unwrap();
|
||||||
let account_id = AccountId::from(&PublicKey::new_from_private_key(&key));
|
let from = AccountId::from(&PublicKey::new_from_private_key(&from_key));
|
||||||
let initial_balance = 100;
|
let initial_balance = 100;
|
||||||
let initial_data = [(account_id, initial_balance)];
|
let initial_data = [(from, initial_balance)];
|
||||||
let mut state =
|
let mut state =
|
||||||
V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
||||||
let from = account_id;
|
let to_key = PrivateKey::try_new([2; 32]).unwrap();
|
||||||
let from_key = key;
|
let to = AccountId::from(&PublicKey::new_from_private_key(&to_key));
|
||||||
let to = AccountId::new([2; 32]);
|
|
||||||
let amount: u128 = 37;
|
let amount: u128 = 37;
|
||||||
|
|
||||||
// Check the recipient is an uninitialized account
|
// Check the recipient is an uninitialized account
|
||||||
@ -2229,26 +2256,80 @@ pub mod tests {
|
|||||||
let expected_recipient_post = Account {
|
let expected_recipient_post = Account {
|
||||||
program_owner: program.id(),
|
program_owner: program.id(),
|
||||||
balance: amount,
|
balance: amount,
|
||||||
|
nonce: Nonce(1),
|
||||||
..Account::default()
|
..Account::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = public_transaction::Message::try_new(
|
let message = public_transaction::Message::try_new(
|
||||||
program.id(),
|
program.id(),
|
||||||
vec![from, to],
|
vec![from, to],
|
||||||
vec![Nonce(0)],
|
vec![Nonce(0), Nonce(0)],
|
||||||
amount,
|
amount,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set =
|
||||||
|
public_transaction::WitnessSet::for_message(&message, &[&from_key, &to_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let recipient_post = state.get_account_by_id(to);
|
let recipient_post = state.get_account_by_id(to);
|
||||||
|
|
||||||
assert_eq!(recipient_post, expected_recipient_post);
|
assert_eq!(recipient_post, expected_recipient_post);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unauthorized_public_account_claiming_fails() {
|
||||||
|
let program = Program::authenticated_transfer_program();
|
||||||
|
let account_key = PrivateKey::try_new([9; 32]).unwrap();
|
||||||
|
let account_id = AccountId::from(&PublicKey::new_from_private_key(&account_key));
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
|
||||||
|
|
||||||
|
assert_eq!(state.get_account_by_id(account_id), Account::default());
|
||||||
|
|
||||||
|
let message =
|
||||||
|
public_transaction::Message::try_new(program.id(), vec![account_id], vec![], 0_u128)
|
||||||
|
.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, 1, 0);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::ProgramExecutionFailed(_))));
|
||||||
|
assert_eq!(state.get_account_by_id(account_id), Account::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authorized_public_account_claiming_succeeds() {
|
||||||
|
let program = Program::authenticated_transfer_program();
|
||||||
|
let account_key = PrivateKey::try_new([10; 32]).unwrap();
|
||||||
|
let account_id = AccountId::from(&PublicKey::new_from_private_key(&account_key));
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
|
||||||
|
|
||||||
|
assert_eq!(state.get_account_by_id(account_id), Account::default());
|
||||||
|
|
||||||
|
let message = public_transaction::Message::try_new(
|
||||||
|
program.id(),
|
||||||
|
vec![account_id],
|
||||||
|
vec![Nonce(0)],
|
||||||
|
0_u128,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&account_key]);
|
||||||
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(account_id),
|
||||||
|
Account {
|
||||||
|
program_owner: program.id(),
|
||||||
|
nonce: Nonce(1),
|
||||||
|
..Account::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn public_chained_call() {
|
fn public_chained_call() {
|
||||||
let program = Program::chain_caller();
|
let program = Program::chain_caller();
|
||||||
@ -2285,7 +2366,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let from_post = state.get_account_by_id(from);
|
let from_post = state.get_account_by_id(from);
|
||||||
let to_post = state.get_account_by_id(to);
|
let to_post = state.get_account_by_id(to);
|
||||||
@ -2325,7 +2406,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
result,
|
result,
|
||||||
Err(NssaError::MaxChainedCallsDepthExceeded)
|
Err(NssaError::MaxChainedCallsDepthExceeded)
|
||||||
@ -2366,7 +2447,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let from_post = state.get_account_by_id(from);
|
let from_post = state.get_account_by_id(from);
|
||||||
let to_post = state.get_account_by_id(to);
|
let to_post = state.get_account_by_id(to);
|
||||||
@ -2382,15 +2463,14 @@ pub mod tests {
|
|||||||
// program and not the chained_caller program.
|
// program and not the chained_caller program.
|
||||||
let chain_caller = Program::chain_caller();
|
let chain_caller = Program::chain_caller();
|
||||||
let auth_transfer = Program::authenticated_transfer_program();
|
let auth_transfer = Program::authenticated_transfer_program();
|
||||||
let key = PrivateKey::try_new([1; 32]).unwrap();
|
let from_key = PrivateKey::try_new([1; 32]).unwrap();
|
||||||
let account_id = AccountId::from(&PublicKey::new_from_private_key(&key));
|
let from = AccountId::from(&PublicKey::new_from_private_key(&from_key));
|
||||||
let initial_balance = 100;
|
let initial_balance = 100;
|
||||||
let initial_data = [(account_id, initial_balance)];
|
let initial_data = [(from, initial_balance)];
|
||||||
let mut state =
|
let mut state =
|
||||||
V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
V03State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs();
|
||||||
let from = account_id;
|
let to_key = PrivateKey::try_new([2; 32]).unwrap();
|
||||||
let from_key = key;
|
let to = AccountId::from(&PublicKey::new_from_private_key(&to_key));
|
||||||
let to = AccountId::new([2; 32]);
|
|
||||||
let amount: u128 = 37;
|
let amount: u128 = 37;
|
||||||
|
|
||||||
// Check the recipient is an uninitialized account
|
// Check the recipient is an uninitialized account
|
||||||
@ -2400,6 +2480,7 @@ pub mod tests {
|
|||||||
// The expected program owner is the authenticated transfer program
|
// The expected program owner is the authenticated transfer program
|
||||||
program_owner: auth_transfer.id(),
|
program_owner: auth_transfer.id(),
|
||||||
balance: amount,
|
balance: amount,
|
||||||
|
nonce: Nonce(1),
|
||||||
..Account::default()
|
..Account::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2415,14 +2496,15 @@ pub mod tests {
|
|||||||
chain_caller.id(),
|
chain_caller.id(),
|
||||||
vec![to, from], // The chain_caller program permutes the account order in the chain
|
vec![to, from], // The chain_caller program permutes the account order in the chain
|
||||||
// call
|
// call
|
||||||
vec![Nonce(0)],
|
vec![Nonce(0), Nonce(0)],
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&from_key]);
|
let witness_set =
|
||||||
|
public_transaction::WitnessSet::for_message(&message, &[&from_key, &to_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let from_post = state.get_account_by_id(from);
|
let from_post = state.get_account_by_id(from);
|
||||||
let to_post = state.get_account_by_id(to);
|
let to_post = state.get_account_by_id(to);
|
||||||
@ -2430,6 +2512,88 @@ pub mod tests {
|
|||||||
assert_eq!(to_post, expected_to_post);
|
assert_eq!(to_post, expected_to_post);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unauthorized_public_account_claiming_fails_when_executed_privately() {
|
||||||
|
let program = Program::authenticated_transfer_program();
|
||||||
|
let account_id = AccountId::new([11; 32]);
|
||||||
|
let public_account = AccountWithMetadata::new(Account::default(), false, account_id);
|
||||||
|
|
||||||
|
let result = execute_and_prove(
|
||||||
|
vec![public_account],
|
||||||
|
Program::serialize_instruction(0_u128).unwrap(),
|
||||||
|
vec![0],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
&program.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(NssaError::ProgramProveFailed(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authorized_public_account_claiming_succeeds_when_executed_privately() {
|
||||||
|
let program = Program::authenticated_transfer_program();
|
||||||
|
let program_id = program.id();
|
||||||
|
let sender_keys = test_private_account_keys_1();
|
||||||
|
let sender_private_account = Account {
|
||||||
|
program_owner: program_id,
|
||||||
|
balance: 100,
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
let sender_commitment = Commitment::new(&sender_keys.npk(), &sender_private_account);
|
||||||
|
let mut state =
|
||||||
|
V03State::new_with_genesis_accounts(&[], std::slice::from_ref(&sender_commitment));
|
||||||
|
let sender_pre = AccountWithMetadata::new(sender_private_account, true, &sender_keys.npk());
|
||||||
|
let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap();
|
||||||
|
let recipient_account_id =
|
||||||
|
AccountId::from(&PublicKey::new_from_private_key(&recipient_private_key));
|
||||||
|
let recipient_pre =
|
||||||
|
AccountWithMetadata::new(Account::default(), true, recipient_account_id);
|
||||||
|
let esk = [5; 32];
|
||||||
|
let shared_secret = SharedSecretKey::new(&esk, &sender_keys.vpk());
|
||||||
|
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||||
|
|
||||||
|
let (output, proof) = execute_and_prove(
|
||||||
|
vec![sender_pre, recipient_pre],
|
||||||
|
Program::serialize_instruction(37_u128).unwrap(),
|
||||||
|
vec![1, 0],
|
||||||
|
vec![(sender_keys.npk(), shared_secret)],
|
||||||
|
vec![sender_keys.nsk],
|
||||||
|
vec![state.get_proof_for_commitment(&sender_commitment)],
|
||||||
|
&program.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let message = Message::try_from_circuit_output(
|
||||||
|
vec![recipient_account_id],
|
||||||
|
vec![Nonce(0)],
|
||||||
|
vec![(sender_keys.npk(), sender_keys.vpk(), epk)],
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_private_key]);
|
||||||
|
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
state
|
||||||
|
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let nullifier = Nullifier::for_account_update(&sender_commitment, &sender_keys.nsk);
|
||||||
|
assert!(state.private_state.1.contains(&nullifier));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.get_account_by_id(recipient_account_id),
|
||||||
|
Account {
|
||||||
|
program_owner: program_id,
|
||||||
|
balance: 37,
|
||||||
|
nonce: Nonce(1),
|
||||||
|
..Account::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case::test_case(1; "single call")]
|
#[test_case::test_case(1; "single call")]
|
||||||
#[test_case::test_case(2; "two calls")]
|
#[test_case::test_case(2; "two calls")]
|
||||||
fn private_chained_call(number_of_calls: u32) {
|
fn private_chained_call(number_of_calls: u32) {
|
||||||
@ -2531,7 +2695,7 @@ pub mod tests {
|
|||||||
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
|
let transaction = PrivacyPreservingTransaction::new(message, witness_set);
|
||||||
|
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&transaction, 1)
|
.transition_from_privacy_preserving_transaction(&transaction, 1, 0)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@ -2571,36 +2735,47 @@ pub mod tests {
|
|||||||
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]);
|
||||||
state.add_pinata_token_program(pinata_definition_id);
|
state.add_pinata_token_program(pinata_definition_id);
|
||||||
|
|
||||||
// Execution of the token program to create new token for the pinata token
|
// Set up the token accounts directly (bypassing public transactions which
|
||||||
// definition and supply accounts
|
// would require signers for Claim::Authorized). The focus of this test is
|
||||||
|
// the PDA mechanism in the pinata program's chained call, not token creation.
|
||||||
let total_supply: u128 = 10_000_000;
|
let total_supply: u128 = 10_000_000;
|
||||||
let instruction = token_core::Instruction::NewFungibleDefinition {
|
let token_definition = token_core::TokenDefinition::Fungible {
|
||||||
name: String::from("PINATA"),
|
name: String::from("PINATA"),
|
||||||
total_supply,
|
total_supply,
|
||||||
|
metadata_id: None,
|
||||||
};
|
};
|
||||||
let message = public_transaction::Message::try_new(
|
let token_holding = token_core::TokenHolding::Fungible {
|
||||||
token.id(),
|
definition_id: pinata_token_definition_id,
|
||||||
vec![pinata_token_definition_id, pinata_token_holding_id],
|
balance: total_supply,
|
||||||
vec![],
|
};
|
||||||
instruction,
|
let winner_holding = token_core::TokenHolding::Fungible {
|
||||||
)
|
definition_id: pinata_token_definition_id,
|
||||||
.unwrap();
|
balance: 0,
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
};
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
state.force_insert_account(
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
pinata_token_definition_id,
|
||||||
|
Account {
|
||||||
// Execution of winner's token holding account initialization
|
program_owner: token.id(),
|
||||||
let instruction = token_core::Instruction::InitializeAccount;
|
data: Data::from(&token_definition),
|
||||||
let message = public_transaction::Message::try_new(
|
..Account::default()
|
||||||
token.id(),
|
},
|
||||||
vec![pinata_token_definition_id, winner_token_holding_id],
|
);
|
||||||
vec![],
|
state.force_insert_account(
|
||||||
instruction,
|
pinata_token_holding_id,
|
||||||
)
|
Account {
|
||||||
.unwrap();
|
program_owner: token.id(),
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
data: Data::from(&token_holding),
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
..Account::default()
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
},
|
||||||
|
);
|
||||||
|
state.force_insert_account(
|
||||||
|
winner_token_holding_id,
|
||||||
|
Account {
|
||||||
|
program_owner: token.id(),
|
||||||
|
data: Data::from(&winner_holding),
|
||||||
|
..Account::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Submit a solution to the pinata program to claim the prize
|
// Submit a solution to the pinata program to claim the prize
|
||||||
let solution: u128 = 989_106;
|
let solution: u128 = 989_106;
|
||||||
@ -2617,7 +2792,7 @@ pub mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id);
|
let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -2647,7 +2822,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
}
|
}
|
||||||
@ -2693,7 +2868,7 @@ pub mod tests {
|
|||||||
|
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[&sender_key]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
let res = state.transition_from_public_transaction(&tx, 1);
|
let res = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(res, Err(NssaError::InvalidProgramBehavior)));
|
||||||
|
|
||||||
let sender_post = state.get_account_by_id(sender_id);
|
let sender_post = state.get_account_by_id(sender_id);
|
||||||
@ -2762,13 +2937,60 @@ pub mod tests {
|
|||||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||||
|
|
||||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||||
let result = state.transition_from_privacy_preserving_transaction(&tx, 1);
|
let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
||||||
assert!(state.private_state.1.contains(&nullifier));
|
assert!(state.private_state.1.contains(&nullifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn private_unauthorized_uninitialized_account_can_still_be_claimed() {
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||||
|
|
||||||
|
let private_keys = test_private_account_keys_1();
|
||||||
|
// This is intentional: claim authorization was introduced to protect public accounts,
|
||||||
|
// especially PDAs. Private PDAs are not useful in practice because there is no way to
|
||||||
|
// operate them without the corresponding private keys, so unauthorized private claiming
|
||||||
|
// remains allowed.
|
||||||
|
let unauthorized_account =
|
||||||
|
AccountWithMetadata::new(Account::default(), false, &private_keys.npk());
|
||||||
|
|
||||||
|
let program = Program::claimer();
|
||||||
|
let esk = [5; 32];
|
||||||
|
let shared_secret = SharedSecretKey::new(&esk, &private_keys.vpk());
|
||||||
|
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||||
|
|
||||||
|
let (output, proof) = execute_and_prove(
|
||||||
|
vec![unauthorized_account],
|
||||||
|
Program::serialize_instruction(0_u128).unwrap(),
|
||||||
|
vec![2],
|
||||||
|
vec![(private_keys.npk(), shared_secret)],
|
||||||
|
vec![],
|
||||||
|
vec![None],
|
||||||
|
&program.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let message = Message::try_from_circuit_output(
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![(private_keys.npk(), private_keys.vpk(), epk)],
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||||
|
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
state
|
||||||
|
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let nullifier = Nullifier::for_account_initialization(&private_keys.npk());
|
||||||
|
assert!(state.private_state.1.contains(&nullifier));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn private_account_claimed_then_used_without_init_flag_should_fail() {
|
fn private_account_claimed_then_used_without_init_flag_should_fail() {
|
||||||
let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||||
@ -2815,7 +3037,7 @@ pub mod tests {
|
|||||||
// Claim should succeed
|
// Claim should succeed
|
||||||
assert!(
|
assert!(
|
||||||
state
|
state
|
||||||
.transition_from_privacy_preserving_transaction(&tx, 1)
|
.transition_from_privacy_preserving_transaction(&tx, 1, 0)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2864,7 +3086,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
// Should succeed - no changes made, no claim needed
|
// Should succeed - no changes made, no claim needed
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -2889,7 +3111,7 @@ pub mod tests {
|
|||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
let result = state.transition_from_public_transaction(&tx, 1);
|
let result = state.transition_from_public_transaction(&tx, 1, 0);
|
||||||
|
|
||||||
// Should fail - cannot modify data without claiming the account
|
// Should fail - cannot modify data without claiming the account
|
||||||
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
assert!(matches!(result, Err(NssaError::InvalidProgramBehavior)));
|
||||||
@ -3018,7 +3240,7 @@ pub mod tests {
|
|||||||
validity_window: (Option<BlockId>, Option<BlockId>),
|
validity_window: (Option<BlockId>, Option<BlockId>),
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) {
|
) {
|
||||||
let validity_window: ValidityWindow = validity_window.try_into().unwrap();
|
let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap();
|
||||||
let validity_window_program = Program::validity_window();
|
let validity_window_program = Program::validity_window();
|
||||||
let account_keys = test_public_account_keys_1();
|
let account_keys = test_public_account_keys_1();
|
||||||
let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id());
|
let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id());
|
||||||
@ -3027,18 +3249,19 @@ pub mod tests {
|
|||||||
let account_ids = vec![pre.account_id];
|
let account_ids = vec![pre.account_id];
|
||||||
let nonces = vec![];
|
let nonces = vec![];
|
||||||
let program_id = validity_window_program.id();
|
let program_id = validity_window_program.id();
|
||||||
let message = public_transaction::Message::try_new(
|
let instruction = (
|
||||||
program_id,
|
block_validity_window,
|
||||||
account_ids,
|
TimestampValidityWindow::new_unbounded(),
|
||||||
nonces,
|
);
|
||||||
validity_window,
|
let message =
|
||||||
)
|
public_transaction::Message::try_new(program_id, account_ids, nonces, instruction)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
PublicTransaction::new(message, witness_set)
|
PublicTransaction::new(message, witness_set)
|
||||||
};
|
};
|
||||||
let result = state.transition_from_public_transaction(&tx, block_id);
|
let result = state.transition_from_public_transaction(&tx, block_id, 0);
|
||||||
let is_inside_validity_window = match (validity_window.start(), validity_window.end()) {
|
let is_inside_validity_window =
|
||||||
|
match (block_validity_window.start(), block_validity_window.end()) {
|
||||||
(Some(s), Some(e)) => s <= block_id && block_id < e,
|
(Some(s), Some(e)) => s <= block_id && block_id < e,
|
||||||
(Some(s), None) => s <= block_id,
|
(Some(s), None) => s <= block_id,
|
||||||
(None, Some(e)) => block_id < e,
|
(None, Some(e)) => block_id < e,
|
||||||
@ -3051,6 +3274,60 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 2; "inside range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 0; "below range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 4; "above range")]
|
||||||
|
#[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")]
|
||||||
|
#[test_case::test_case((Some(1), None), 10; "lower bound only - above")]
|
||||||
|
#[test_case::test_case((Some(1), None), 0; "lower bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 0; "upper bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 4; "upper bound only - above")]
|
||||||
|
#[test_case::test_case((None, None), 0; "no bounds - always valid")]
|
||||||
|
#[test_case::test_case((None, None), 100; "no bounds - always valid 2")]
|
||||||
|
fn timestamp_validity_window_works_in_public_transactions(
|
||||||
|
validity_window: (Option<Timestamp>, Option<Timestamp>),
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) {
|
||||||
|
let timestamp_validity_window: TimestampValidityWindow =
|
||||||
|
validity_window.try_into().unwrap();
|
||||||
|
let validity_window_program = Program::validity_window();
|
||||||
|
let account_keys = test_public_account_keys_1();
|
||||||
|
let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id());
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||||
|
let tx = {
|
||||||
|
let account_ids = vec![pre.account_id];
|
||||||
|
let nonces = vec![];
|
||||||
|
let program_id = validity_window_program.id();
|
||||||
|
let instruction = (
|
||||||
|
BlockValidityWindow::new_unbounded(),
|
||||||
|
timestamp_validity_window,
|
||||||
|
);
|
||||||
|
let message =
|
||||||
|
public_transaction::Message::try_new(program_id, account_ids, nonces, instruction)
|
||||||
|
.unwrap();
|
||||||
|
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]);
|
||||||
|
PublicTransaction::new(message, witness_set)
|
||||||
|
};
|
||||||
|
let result = state.transition_from_public_transaction(&tx, 1, timestamp);
|
||||||
|
let is_inside_validity_window = match (
|
||||||
|
timestamp_validity_window.start(),
|
||||||
|
timestamp_validity_window.end(),
|
||||||
|
) {
|
||||||
|
(Some(s), Some(e)) => s <= timestamp && timestamp < e,
|
||||||
|
(Some(s), None) => s <= timestamp,
|
||||||
|
(None, Some(e)) => timestamp < e,
|
||||||
|
(None, None) => true,
|
||||||
|
};
|
||||||
|
if is_inside_validity_window {
|
||||||
|
assert!(result.is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(matches!(result, Err(NssaError::OutOfValidityWindow)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")]
|
#[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")]
|
||||||
#[test_case::test_case((Some(1), Some(3)), 2; "inside range")]
|
#[test_case::test_case((Some(1), Some(3)), 2; "inside range")]
|
||||||
#[test_case::test_case((Some(1), Some(3)), 0; "below range")]
|
#[test_case::test_case((Some(1), Some(3)), 0; "below range")]
|
||||||
@ -3068,7 +3345,7 @@ pub mod tests {
|
|||||||
validity_window: (Option<BlockId>, Option<BlockId>),
|
validity_window: (Option<BlockId>, Option<BlockId>),
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) {
|
) {
|
||||||
let validity_window: ValidityWindow = validity_window.try_into().unwrap();
|
let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap();
|
||||||
let validity_window_program = Program::validity_window();
|
let validity_window_program = Program::validity_window();
|
||||||
let account_keys = test_private_account_keys_1();
|
let account_keys = test_private_account_keys_1();
|
||||||
let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk());
|
let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk());
|
||||||
@ -3078,9 +3355,13 @@ pub mod tests {
|
|||||||
let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk());
|
let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk());
|
||||||
let epk = EphemeralPublicKey::from_scalar(esk);
|
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||||
|
|
||||||
|
let instruction = (
|
||||||
|
block_validity_window,
|
||||||
|
TimestampValidityWindow::new_unbounded(),
|
||||||
|
);
|
||||||
let (output, proof) = circuit::execute_and_prove(
|
let (output, proof) = circuit::execute_and_prove(
|
||||||
vec![pre],
|
vec![pre],
|
||||||
Program::serialize_instruction(validity_window).unwrap(),
|
Program::serialize_instruction(instruction).unwrap(),
|
||||||
vec![2],
|
vec![2],
|
||||||
vec![(account_keys.npk(), shared_secret)],
|
vec![(account_keys.npk(), shared_secret)],
|
||||||
vec![],
|
vec![],
|
||||||
@ -3100,8 +3381,9 @@ pub mod tests {
|
|||||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||||
PrivacyPreservingTransaction::new(message, witness_set)
|
PrivacyPreservingTransaction::new(message, witness_set)
|
||||||
};
|
};
|
||||||
let result = state.transition_from_privacy_preserving_transaction(&tx, block_id);
|
let result = state.transition_from_privacy_preserving_transaction(&tx, block_id, 0);
|
||||||
let is_inside_validity_window = match (validity_window.start(), validity_window.end()) {
|
let is_inside_validity_window =
|
||||||
|
match (block_validity_window.start(), block_validity_window.end()) {
|
||||||
(Some(s), Some(e)) => s <= block_id && block_id < e,
|
(Some(s), Some(e)) => s <= block_id && block_id < e,
|
||||||
(Some(s), None) => s <= block_id,
|
(Some(s), None) => s <= block_id,
|
||||||
(None, Some(e)) => block_id < e,
|
(None, Some(e)) => block_id < e,
|
||||||
@ -3114,6 +3396,77 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 3; "at upper bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 2; "inside range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 0; "below range")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 1; "at lower bound")]
|
||||||
|
#[test_case::test_case((Some(1), Some(3)), 4; "above range")]
|
||||||
|
#[test_case::test_case((Some(1), None), 1; "lower bound only - at bound")]
|
||||||
|
#[test_case::test_case((Some(1), None), 10; "lower bound only - above")]
|
||||||
|
#[test_case::test_case((Some(1), None), 0; "lower bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 3; "upper bound only - at bound")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 0; "upper bound only - below")]
|
||||||
|
#[test_case::test_case((None, Some(3)), 4; "upper bound only - above")]
|
||||||
|
#[test_case::test_case((None, None), 0; "no bounds - always valid")]
|
||||||
|
#[test_case::test_case((None, None), 100; "no bounds - always valid 2")]
|
||||||
|
fn timestamp_validity_window_works_in_privacy_preserving_transactions(
|
||||||
|
validity_window: (Option<Timestamp>, Option<Timestamp>),
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) {
|
||||||
|
let timestamp_validity_window: TimestampValidityWindow =
|
||||||
|
validity_window.try_into().unwrap();
|
||||||
|
let validity_window_program = Program::validity_window();
|
||||||
|
let account_keys = test_private_account_keys_1();
|
||||||
|
let pre = AccountWithMetadata::new(Account::default(), false, &account_keys.npk());
|
||||||
|
let mut state = V03State::new_with_genesis_accounts(&[], &[]).with_test_programs();
|
||||||
|
let tx = {
|
||||||
|
let esk = [3; 32];
|
||||||
|
let shared_secret = SharedSecretKey::new(&esk, &account_keys.vpk());
|
||||||
|
let epk = EphemeralPublicKey::from_scalar(esk);
|
||||||
|
|
||||||
|
let instruction = (
|
||||||
|
BlockValidityWindow::new_unbounded(),
|
||||||
|
timestamp_validity_window,
|
||||||
|
);
|
||||||
|
let (output, proof) = circuit::execute_and_prove(
|
||||||
|
vec![pre],
|
||||||
|
Program::serialize_instruction(instruction).unwrap(),
|
||||||
|
vec![2],
|
||||||
|
vec![(account_keys.npk(), shared_secret)],
|
||||||
|
vec![],
|
||||||
|
vec![None],
|
||||||
|
&validity_window_program.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let message = Message::try_from_circuit_output(
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
vec![(account_keys.npk(), account_keys.vpk(), epk)],
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||||
|
PrivacyPreservingTransaction::new(message, witness_set)
|
||||||
|
};
|
||||||
|
let result = state.transition_from_privacy_preserving_transaction(&tx, 1, timestamp);
|
||||||
|
let is_inside_validity_window = match (
|
||||||
|
timestamp_validity_window.start(),
|
||||||
|
timestamp_validity_window.end(),
|
||||||
|
) {
|
||||||
|
(Some(s), Some(e)) => s <= timestamp && timestamp < e,
|
||||||
|
(Some(s), None) => s <= timestamp,
|
||||||
|
(None, Some(e)) => timestamp < e,
|
||||||
|
(None, None) => true,
|
||||||
|
};
|
||||||
|
if is_inside_validity_window {
|
||||||
|
assert!(result.is_ok());
|
||||||
|
} else {
|
||||||
|
assert!(matches!(result, Err(NssaError::OutOfValidityWindow)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_serialization_roundtrip() {
|
fn state_serialization_roundtrip() {
|
||||||
let account_id_1 = AccountId::new([1; 32]);
|
let account_id_1 = AccountId::new([1; 32]);
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
AccountPostState, Claim, DEFAULT_PROGRAM_ID, ProgramInput, ProgramOutput, read_nssa_inputs,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initializes a default account under the ownership of this program.
|
/// Initializes a default account under the ownership of this program.
|
||||||
fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState {
|
fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState {
|
||||||
let account_to_claim = AccountPostState::new_claimed(pre_state.account);
|
let account_to_claim = AccountPostState::new_claimed(pre_state.account, Claim::Authorized);
|
||||||
let is_authorized = pre_state.is_authorized;
|
let is_authorized = pre_state.is_authorized;
|
||||||
|
|
||||||
// Continue only if the account to claim has default values
|
// Continue only if the account to claim has default values
|
||||||
@ -52,7 +52,7 @@ fn transfer(
|
|||||||
|
|
||||||
// Claim recipient account if it has default program owner
|
// Claim recipient account if it has default program owner
|
||||||
if recipient_post_account.program_owner == DEFAULT_PROGRAM_ID {
|
if recipient_post_account.program_owner == DEFAULT_PROGRAM_ID {
|
||||||
AccountPostState::new_claimed(recipient_post_account)
|
AccountPostState::new_claimed(recipient_post_account, Claim::Authorized)
|
||||||
} else {
|
} else {
|
||||||
AccountPostState::new(recipient_post_account)
|
AccountPostState::new(recipient_post_account)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
use risc0_zkvm::sha::{Impl, Sha256 as _};
|
||||||
|
|
||||||
const PRIZE: u128 = 150;
|
const PRIZE: u128 = 150;
|
||||||
@ -82,7 +82,7 @@ fn main() {
|
|||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pinata, winner],
|
vec![pinata, winner],
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new_claimed_if_default(pinata_post),
|
AccountPostState::new_claimed_if_default(pinata_post, Claim::Authorized),
|
||||||
AccountPostState::new(winner_post),
|
AccountPostState::new(winner_post),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,8 +10,9 @@ use nssa_core::{
|
|||||||
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
account::{Account, AccountId, AccountWithMetadata, Nonce},
|
||||||
compute_digest_for_path,
|
compute_digest_for_path,
|
||||||
program::{
|
program::{
|
||||||
AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId,
|
AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID,
|
||||||
ProgramOutput, ValidityWindow, validate_execution,
|
MAX_NUMBER_CHAINED_CALLS, ProgramId, ProgramOutput, TimestampValidityWindow,
|
||||||
|
validate_execution,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use risc0_zkvm::{guest::env, serde::to_vec};
|
use risc0_zkvm::{guest::env, serde::to_vec};
|
||||||
@ -20,29 +21,51 @@ use risc0_zkvm::{guest::env, serde::to_vec};
|
|||||||
struct ExecutionState {
|
struct ExecutionState {
|
||||||
pre_states: Vec<AccountWithMetadata>,
|
pre_states: Vec<AccountWithMetadata>,
|
||||||
post_states: HashMap<AccountId, Account>,
|
post_states: HashMap<AccountId, Account>,
|
||||||
validity_window: ValidityWindow,
|
block_validity_window: BlockValidityWindow,
|
||||||
|
timestamp_validity_window: TimestampValidityWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionState {
|
impl ExecutionState {
|
||||||
/// Validate program outputs and derive the overall execution state.
|
/// Validate program outputs and derive the overall execution state.
|
||||||
pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec<ProgramOutput>) -> Self {
|
pub fn derive_from_outputs(
|
||||||
let valid_from_id = program_outputs
|
visibility_mask: &[u8],
|
||||||
|
program_id: ProgramId,
|
||||||
|
program_outputs: Vec<ProgramOutput>,
|
||||||
|
) -> Self {
|
||||||
|
let block_valid_from = program_outputs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|output| output.validity_window.start())
|
.filter_map(|output| output.block_validity_window.start())
|
||||||
.max();
|
.max();
|
||||||
let valid_until_id = program_outputs
|
let block_valid_until = program_outputs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|output| output.validity_window.end())
|
.filter_map(|output| output.block_validity_window.end())
|
||||||
|
.min();
|
||||||
|
let ts_valid_from = program_outputs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|output| output.timestamp_validity_window.start())
|
||||||
|
.max();
|
||||||
|
let ts_valid_until = program_outputs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|output| output.timestamp_validity_window.end())
|
||||||
.min();
|
.min();
|
||||||
|
|
||||||
let validity_window = (valid_from_id, valid_until_id).try_into().expect(
|
let block_validity_window: BlockValidityWindow = (block_valid_from, block_valid_until)
|
||||||
"There should be non empty intersection in the program output validity windows",
|
.try_into()
|
||||||
|
.expect(
|
||||||
|
"There should be non empty intersection in the program output block validity windows",
|
||||||
|
);
|
||||||
|
let timestamp_validity_window: TimestampValidityWindow =
|
||||||
|
(ts_valid_from, ts_valid_until)
|
||||||
|
.try_into()
|
||||||
|
.expect(
|
||||||
|
"There should be non empty intersection in the program output timestamp validity windows",
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut execution_state = Self {
|
let mut execution_state = Self {
|
||||||
pre_states: Vec::new(),
|
pre_states: Vec::new(),
|
||||||
post_states: HashMap::new(),
|
post_states: HashMap::new(),
|
||||||
validity_window,
|
block_validity_window,
|
||||||
|
timestamp_validity_window,
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(first_output) = program_outputs.first() else {
|
let Some(first_output) = program_outputs.first() else {
|
||||||
@ -102,6 +125,7 @@ impl ExecutionState {
|
|||||||
&chained_call.pda_seeds,
|
&chained_call.pda_seeds,
|
||||||
);
|
);
|
||||||
execution_state.validate_and_sync_states(
|
execution_state.validate_and_sync_states(
|
||||||
|
visibility_mask,
|
||||||
chained_call.program_id,
|
chained_call.program_id,
|
||||||
&authorized_pdas,
|
&authorized_pdas,
|
||||||
program_output.pre_states,
|
program_output.pre_states,
|
||||||
@ -134,7 +158,7 @@ impl ExecutionState {
|
|||||||
{
|
{
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
post.program_owner, DEFAULT_PROGRAM_ID,
|
post.program_owner, DEFAULT_PROGRAM_ID,
|
||||||
"Account {account_id:?} was modified but not claimed"
|
"Account {account_id} was modified but not claimed"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +168,7 @@ impl ExecutionState {
|
|||||||
/// Validate program pre and post states and populate the execution state.
|
/// Validate program pre and post states and populate the execution state.
|
||||||
fn validate_and_sync_states(
|
fn validate_and_sync_states(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
visibility_mask: &[u8],
|
||||||
program_id: ProgramId,
|
program_id: ProgramId,
|
||||||
authorized_pdas: &HashSet<AccountId>,
|
authorized_pdas: &HashSet<AccountId>,
|
||||||
pre_states: Vec<AccountWithMetadata>,
|
pre_states: Vec<AccountWithMetadata>,
|
||||||
@ -151,14 +176,25 @@ impl ExecutionState {
|
|||||||
) {
|
) {
|
||||||
for (pre, mut post) in pre_states.into_iter().zip(post_states) {
|
for (pre, mut post) in pre_states.into_iter().zip(post_states) {
|
||||||
let pre_account_id = pre.account_id;
|
let pre_account_id = pre.account_id;
|
||||||
|
let pre_is_authorized = pre.is_authorized;
|
||||||
let post_states_entry = self.post_states.entry(pre.account_id);
|
let post_states_entry = self.post_states.entry(pre.account_id);
|
||||||
match &post_states_entry {
|
match &post_states_entry {
|
||||||
Entry::Occupied(occupied) => {
|
Entry::Occupied(occupied) => {
|
||||||
|
#[expect(
|
||||||
|
clippy::shadow_unrelated,
|
||||||
|
reason = "Shadowing is intentional to use all fields"
|
||||||
|
)]
|
||||||
|
let AccountWithMetadata {
|
||||||
|
account: pre_account,
|
||||||
|
account_id: pre_account_id,
|
||||||
|
is_authorized: pre_is_authorized,
|
||||||
|
} = pre;
|
||||||
|
|
||||||
// Ensure that new pre state is the same as known post state
|
// Ensure that new pre state is the same as known post state
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
occupied.get(),
|
occupied.get(),
|
||||||
&pre.account,
|
&pre_account,
|
||||||
"Inconsistent pre state for account {pre_account_id:?}",
|
"Inconsistent pre state for account {pre_account_id}",
|
||||||
);
|
);
|
||||||
|
|
||||||
let previous_is_authorized = self
|
let previous_is_authorized = self
|
||||||
@ -167,7 +203,7 @@ impl ExecutionState {
|
|||||||
.find(|acc| acc.account_id == pre_account_id)
|
.find(|acc| acc.account_id == pre_account_id)
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|| panic!(
|
|| panic!(
|
||||||
"Pre state must exist in execution state for account {pre_account_id:?}",
|
"Pre state must exist in execution state for account {pre_account_id}",
|
||||||
),
|
),
|
||||||
|acc| acc.is_authorized
|
|acc| acc.is_authorized
|
||||||
);
|
);
|
||||||
@ -176,22 +212,57 @@ impl ExecutionState {
|
|||||||
previous_is_authorized || authorized_pdas.contains(&pre_account_id);
|
previous_is_authorized || authorized_pdas.contains(&pre_account_id);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pre.is_authorized, is_authorized,
|
pre_is_authorized, is_authorized,
|
||||||
"Inconsistent authorization for account {pre_account_id:?}",
|
"Inconsistent authorization for account {pre_account_id}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Entry::Vacant(_) => {
|
Entry::Vacant(_) => {
|
||||||
|
// Pre state for the initial call
|
||||||
self.pre_states.push(pre);
|
self.pre_states.push(pre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if post.requires_claim() {
|
if let Some(claim) = post.required_claim() {
|
||||||
// The invoked program can only claim accounts with default program id.
|
// The invoked program can only claim accounts with default program id.
|
||||||
if post.account().program_owner == DEFAULT_PROGRAM_ID {
|
assert_eq!(
|
||||||
post.account_mut().program_owner = program_id;
|
post.account().program_owner,
|
||||||
} else {
|
DEFAULT_PROGRAM_ID,
|
||||||
panic!("Cannot claim an initialized account {pre_account_id:?}");
|
"Cannot claim an initialized account {pre_account_id}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let pre_state_position = self
|
||||||
|
.pre_states
|
||||||
|
.iter()
|
||||||
|
.position(|acc| acc.account_id == pre_account_id)
|
||||||
|
.expect("Pre state must exist at this point");
|
||||||
|
|
||||||
|
let is_public_account = visibility_mask[pre_state_position] == 0;
|
||||||
|
if is_public_account {
|
||||||
|
match claim {
|
||||||
|
Claim::Authorized => {
|
||||||
|
// Note: no need to check authorized pdas because we have already
|
||||||
|
// checked consistency of authorization above.
|
||||||
|
assert!(
|
||||||
|
pre_is_authorized,
|
||||||
|
"Cannot claim unauthorized account {pre_account_id}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Claim::Pda(seed) => {
|
||||||
|
let pda = AccountId::from((&program_id, &seed));
|
||||||
|
assert_eq!(
|
||||||
|
pre_account_id, pda,
|
||||||
|
"Invalid PDA claim for account {pre_account_id} which does not match derived PDA {pda}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We don't care about the exact claim mechanism for private accounts.
|
||||||
|
// This is because the main reason to have it is to protect against PDA griefing
|
||||||
|
// attacks in public execution, while private PDA doesn't make much sense
|
||||||
|
// anyway.
|
||||||
|
}
|
||||||
|
|
||||||
|
post.account_mut().program_owner = program_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
post_states_entry.insert_entry(post.into_account());
|
post_states_entry.insert_entry(post.into_account());
|
||||||
@ -225,7 +296,8 @@ fn compute_circuit_output(
|
|||||||
ciphertexts: Vec::new(),
|
ciphertexts: Vec::new(),
|
||||||
new_commitments: Vec::new(),
|
new_commitments: Vec::new(),
|
||||||
new_nullifiers: Vec::new(),
|
new_nullifiers: Vec::new(),
|
||||||
validity_window: execution_state.validity_window,
|
block_validity_window: execution_state.block_validity_window,
|
||||||
|
timestamp_validity_window: execution_state.timestamp_validity_window,
|
||||||
};
|
};
|
||||||
|
|
||||||
let states_iter = execution_state.into_states_iter();
|
let states_iter = execution_state.into_states_iter();
|
||||||
@ -408,7 +480,8 @@ fn main() {
|
|||||||
program_id,
|
program_id,
|
||||||
} = env::read();
|
} = env::read();
|
||||||
|
|
||||||
let execution_state = ExecutionState::derive_from_outputs(program_id, program_outputs);
|
let execution_state =
|
||||||
|
ExecutionState::derive_from_outputs(&visibility_mask, program_id, program_outputs);
|
||||||
|
|
||||||
let output = compute_circuit_output(
|
let output = compute_circuit_output(
|
||||||
execution_state,
|
execution_state,
|
||||||
|
|||||||
@ -2,11 +2,11 @@ use std::num::NonZeroU128;
|
|||||||
|
|
||||||
use amm_core::{
|
use amm_core::{
|
||||||
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
|
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
|
||||||
compute_pool_pda, compute_vault_pda,
|
compute_pool_pda, compute_pool_pda_seed, compute_vault_pda, compute_vault_pda_seed,
|
||||||
};
|
};
|
||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::{AccountPostState, ChainedCall, ProgramId},
|
program::{AccountPostState, ChainedCall, Claim, ProgramId},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
|
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
|
||||||
@ -108,36 +108,52 @@ pub fn new_definition(
|
|||||||
};
|
};
|
||||||
|
|
||||||
pool_post.data = Data::from(&pool_post_definition);
|
pool_post.data = Data::from(&pool_post_definition);
|
||||||
let pool_post = AccountPostState::new_claimed_if_default(pool_post);
|
let pool_pda_seed = compute_pool_pda_seed(definition_token_a_id, definition_token_b_id);
|
||||||
|
let pool_post = AccountPostState::new_claimed_if_default(pool_post, Claim::Pda(pool_pda_seed));
|
||||||
|
|
||||||
let token_program_id = user_holding_a.account.program_owner;
|
let token_program_id = user_holding_a.account.program_owner;
|
||||||
|
|
||||||
// Chain call for Token A (user_holding_a -> Vault_A)
|
// Chain call for Token A (user_holding_a -> Vault_A)
|
||||||
|
let vault_a_seed = compute_vault_pda_seed(pool.account_id, definition_token_a_id);
|
||||||
|
let vault_a_authorized = AccountWithMetadata {
|
||||||
|
is_authorized: true,
|
||||||
|
..vault_a.clone()
|
||||||
|
};
|
||||||
let call_token_a = ChainedCall::new(
|
let call_token_a = ChainedCall::new(
|
||||||
token_program_id,
|
token_program_id,
|
||||||
vec![user_holding_a.clone(), vault_a.clone()],
|
vec![user_holding_a.clone(), vault_a_authorized],
|
||||||
&token_core::Instruction::Transfer {
|
&token_core::Instruction::Transfer {
|
||||||
amount_to_transfer: token_a_amount.into(),
|
amount_to_transfer: token_a_amount.into(),
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.with_pda_seeds(vec![vault_a_seed]);
|
||||||
|
|
||||||
// Chain call for Token B (user_holding_b -> Vault_B)
|
// Chain call for Token B (user_holding_b -> Vault_B)
|
||||||
|
let vault_b_seed = compute_vault_pda_seed(pool.account_id, definition_token_b_id);
|
||||||
|
let vault_b_authorized = AccountWithMetadata {
|
||||||
|
is_authorized: true,
|
||||||
|
..vault_b.clone()
|
||||||
|
};
|
||||||
let call_token_b = ChainedCall::new(
|
let call_token_b = ChainedCall::new(
|
||||||
token_program_id,
|
token_program_id,
|
||||||
vec![user_holding_b.clone(), vault_b.clone()],
|
vec![user_holding_b.clone(), vault_b_authorized],
|
||||||
&token_core::Instruction::Transfer {
|
&token_core::Instruction::Transfer {
|
||||||
amount_to_transfer: token_b_amount.into(),
|
amount_to_transfer: token_b_amount.into(),
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.with_pda_seeds(vec![vault_b_seed]);
|
||||||
let mut pool_lp_auth = pool_definition_lp.clone();
|
|
||||||
pool_lp_auth.is_authorized = true;
|
|
||||||
|
|
||||||
|
let pool_lp_pda_seed = compute_liquidity_token_pda_seed(pool.account_id);
|
||||||
|
let pool_lp_authorized = AccountWithMetadata {
|
||||||
|
is_authorized: true,
|
||||||
|
..pool_definition_lp.clone()
|
||||||
|
};
|
||||||
let call_token_lp = ChainedCall::new(
|
let call_token_lp = ChainedCall::new(
|
||||||
token_program_id,
|
token_program_id,
|
||||||
vec![pool_lp_auth, user_holding_lp.clone()],
|
vec![pool_lp_authorized, user_holding_lp.clone()],
|
||||||
&instruction,
|
&instruction,
|
||||||
)
|
)
|
||||||
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(pool.account_id)]);
|
.with_pda_seeds(vec![pool_lp_pda_seed]);
|
||||||
|
|
||||||
let chained_calls = vec![call_token_lp, call_token_b, call_token_a];
|
let chained_calls = vec![call_token_lp, call_token_b, call_token_a];
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::num::NonZero;
|
use std::{num::NonZero, vec};
|
||||||
|
|
||||||
use amm_core::{
|
use amm_core::{
|
||||||
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
|
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
|
||||||
@ -1756,7 +1756,7 @@ impl AccountsForExeTests {
|
|||||||
definition_id: IdForExeTests::token_lp_definition_id(),
|
definition_id: IdForExeTests::token_lp_definition_id(),
|
||||||
balance: BalanceForExeTests::lp_supply_init(),
|
balance: BalanceForExeTests::lp_supply_init(),
|
||||||
}),
|
}),
|
||||||
nonce: 0_u128.into(),
|
nonce: 1_u128.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1801,7 +1801,7 @@ impl AccountsForExeTests {
|
|||||||
definition_id: IdForExeTests::token_lp_definition_id(),
|
definition_id: IdForExeTests::token_lp_definition_id(),
|
||||||
balance: 0,
|
balance: 0,
|
||||||
}),
|
}),
|
||||||
nonce: 0_u128.into(),
|
nonce: 1.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2733,7 +2733,7 @@ fn simple_amm_remove() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -2799,7 +2799,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
|
|||||||
IdForExeTests::user_token_b_id(),
|
IdForExeTests::user_token_b_id(),
|
||||||
IdForExeTests::user_token_lp_id(),
|
IdForExeTests::user_token_lp_id(),
|
||||||
],
|
],
|
||||||
vec![0_u128.into(), 0_u128.into()],
|
vec![0_u128.into(), 0_u128.into(), 0_u128.into()],
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -2809,11 +2809,12 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() {
|
|||||||
&[
|
&[
|
||||||
&PrivateKeysForTests::user_token_a_key(),
|
&PrivateKeysForTests::user_token_a_key(),
|
||||||
&PrivateKeysForTests::user_token_b_key(),
|
&PrivateKeysForTests::user_token_b_key(),
|
||||||
|
&PrivateKeysForTests::user_token_lp_key(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -2897,7 +2898,7 @@ fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -2955,7 +2956,7 @@ fn simple_amm_new_definition_uninitialized_pool() {
|
|||||||
IdForExeTests::user_token_b_id(),
|
IdForExeTests::user_token_b_id(),
|
||||||
IdForExeTests::user_token_lp_id(),
|
IdForExeTests::user_token_lp_id(),
|
||||||
],
|
],
|
||||||
vec![0_u128.into(), 0_u128.into()],
|
vec![0_u128.into(), 0_u128.into(), 0_u128.into()],
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -2965,11 +2966,12 @@ fn simple_amm_new_definition_uninitialized_pool() {
|
|||||||
&[
|
&[
|
||||||
&PrivateKeysForTests::user_token_a_key(),
|
&PrivateKeysForTests::user_token_a_key(),
|
||||||
&PrivateKeysForTests::user_token_b_key(),
|
&PrivateKeysForTests::user_token_b_key(),
|
||||||
|
&PrivateKeysForTests::user_token_lp_key(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -3031,7 +3033,7 @@ fn simple_amm_add() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -3088,7 +3090,7 @@ fn simple_amm_swap_1() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
@ -3138,7 +3140,7 @@ fn simple_amm_swap_2() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
state.transition_from_public_transaction(&tx, 1).unwrap();
|
state.transition_from_public_transaction(&tx, 1, 0).unwrap();
|
||||||
|
|
||||||
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
let pool_post = state.get_account_by_id(IdForExeTests::pool_definition_id());
|
||||||
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
let vault_a_post = state.get_account_by_id(IdForExeTests::vault_a_id());
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata},
|
account::{Account, AccountWithMetadata},
|
||||||
program::{AccountPostState, ChainedCall, ProgramId},
|
program::{AccountPostState, ChainedCall, Claim, ProgramId},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create_associated_token_account(
|
pub fn create_associated_token_account(
|
||||||
@ -11,7 +11,7 @@ pub fn create_associated_token_account(
|
|||||||
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
||||||
// No authorization check needed: create is idempotent, so anyone can call it safely.
|
// No authorization check needed: create is idempotent, so anyone can call it safely.
|
||||||
let token_program_id = token_definition.account.program_owner;
|
let token_program_id = token_definition.account.program_owner;
|
||||||
ata_core::verify_ata_and_get_seed(
|
let ata_seed = ata_core::verify_ata_and_get_seed(
|
||||||
&ata_account,
|
&ata_account,
|
||||||
&owner,
|
&owner,
|
||||||
token_definition.account_id,
|
token_definition.account_id,
|
||||||
@ -22,7 +22,7 @@ pub fn create_associated_token_account(
|
|||||||
if ata_account.account != Account::default() {
|
if ata_account.account != Account::default() {
|
||||||
return (
|
return (
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new_claimed_if_default(owner.account.clone()),
|
AccountPostState::new_claimed_if_default(owner.account.clone(), Claim::Authorized),
|
||||||
AccountPostState::new(token_definition.account.clone()),
|
AccountPostState::new(token_definition.account.clone()),
|
||||||
AccountPostState::new(ata_account.account.clone()),
|
AccountPostState::new(ata_account.account.clone()),
|
||||||
],
|
],
|
||||||
@ -31,14 +31,20 @@ pub fn create_associated_token_account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let post_states = vec![
|
let post_states = vec![
|
||||||
AccountPostState::new_claimed_if_default(owner.account.clone()),
|
AccountPostState::new_claimed_if_default(owner.account.clone(), Claim::Authorized),
|
||||||
AccountPostState::new(token_definition.account.clone()),
|
AccountPostState::new(token_definition.account.clone()),
|
||||||
AccountPostState::new(ata_account.account.clone()),
|
AccountPostState::new(ata_account.account.clone()),
|
||||||
];
|
];
|
||||||
|
let ata_account_auth = AccountWithMetadata {
|
||||||
|
is_authorized: true,
|
||||||
|
..ata_account.clone()
|
||||||
|
};
|
||||||
let chained_call = ChainedCall::new(
|
let chained_call = ChainedCall::new(
|
||||||
token_program_id,
|
token_program_id,
|
||||||
vec![token_definition.clone(), ata_account.clone()],
|
vec![token_definition.clone(), ata_account_auth],
|
||||||
&token_core::Instruction::InitializeAccount,
|
&token_core::Instruction::InitializeAccount,
|
||||||
);
|
)
|
||||||
|
.with_pda_seeds(vec![ata_seed]);
|
||||||
|
|
||||||
(post_states, vec![chained_call])
|
(post_states, vec![chained_call])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,23 +10,23 @@ pub enum Instruction {
|
|||||||
/// Transfer tokens from sender to recipient.
|
/// Transfer tokens from sender to recipient.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - Sender's Token Holding account (authorized),
|
/// - Sender's Token Holding account (initialized, authorized),
|
||||||
/// - Recipient's Token Holding account.
|
/// - Recipient's Token Holding account (initialized or authorized and uninitialized).
|
||||||
Transfer { amount_to_transfer: u128 },
|
Transfer { amount_to_transfer: u128 },
|
||||||
|
|
||||||
/// Create a new fungible token definition without metadata.
|
/// Create a new fungible token definition without metadata.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - Token Definition account (uninitialized),
|
/// - Token Definition account (uninitialized, authorized),
|
||||||
/// - Token Holding account (uninitialized).
|
/// - Token Holding account (uninitialized, authorized).
|
||||||
NewFungibleDefinition { name: String, total_supply: u128 },
|
NewFungibleDefinition { name: String, total_supply: u128 },
|
||||||
|
|
||||||
/// Create a new fungible or non-fungible token definition with metadata.
|
/// Create a new fungible or non-fungible token definition with metadata.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - Token Definition account (uninitialized),
|
/// - Token Definition account (uninitialized, authorized),
|
||||||
/// - Token Holding account (uninitialized),
|
/// - Token Holding account (uninitialized, authorized),
|
||||||
/// - Token Metadata account (uninitialized).
|
/// - Token Metadata account (uninitialized, authorized).
|
||||||
NewDefinitionWithMetadata {
|
NewDefinitionWithMetadata {
|
||||||
new_definition: NewTokenDefinition,
|
new_definition: NewTokenDefinition,
|
||||||
/// Boxed to avoid large enum variant size.
|
/// Boxed to avoid large enum variant size.
|
||||||
@ -36,29 +36,29 @@ pub enum Instruction {
|
|||||||
/// Initialize a token holding account for a given token definition.
|
/// Initialize a token holding account for a given token definition.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - Token Definition account (initialized),
|
/// - Token Definition account (initialized, any authorization),
|
||||||
/// - Token Holding account (uninitialized),
|
/// - Token Holding account (uninitialized, authorized),
|
||||||
InitializeAccount,
|
InitializeAccount,
|
||||||
|
|
||||||
/// Burn tokens from the holder's account.
|
/// Burn tokens from the holder's account.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - Token Definition account (initialized),
|
/// - Token Definition account (initialized, any authorization),
|
||||||
/// - Token Holding account (authorized).
|
/// - Token Holding account (initialized, authorized).
|
||||||
Burn { amount_to_burn: u128 },
|
Burn { amount_to_burn: u128 },
|
||||||
|
|
||||||
/// Mint new tokens to the holder's account.
|
/// Mint new tokens to the holder's account.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - Token Definition account (authorized),
|
/// - Token Definition account (initialized, authorized),
|
||||||
/// - Token Holding account (uninitialized or initialized).
|
/// - Token Holding account (uninitialized or authorized and initialized).
|
||||||
Mint { amount_to_mint: u128 },
|
Mint { amount_to_mint: u128 },
|
||||||
|
|
||||||
/// Print a new NFT from the master copy.
|
/// Print a new NFT from the master copy.
|
||||||
///
|
///
|
||||||
/// Required accounts:
|
/// Required accounts:
|
||||||
/// - NFT Master Token Holding account (authorized),
|
/// - NFT Master Token Holding account (authorized),
|
||||||
/// - NFT Printed Copy Token Holding account (uninitialized).
|
/// - NFT Printed Copy Token Holding account (uninitialized, authorized).
|
||||||
PrintNft,
|
PrintNft,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::AccountPostState,
|
program::{AccountPostState, Claim},
|
||||||
};
|
};
|
||||||
use token_core::{TokenDefinition, TokenHolding};
|
use token_core::{TokenDefinition, TokenHolding};
|
||||||
|
|
||||||
@ -30,6 +30,6 @@ pub fn initialize_account(
|
|||||||
|
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(definition_post),
|
AccountPostState::new(definition_post),
|
||||||
AccountPostState::new_claimed(account_to_initialize),
|
AccountPostState::new_claimed(account_to_initialize, Claim::Authorized),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::AccountPostState,
|
program::{AccountPostState, Claim},
|
||||||
};
|
};
|
||||||
use token_core::{TokenDefinition, TokenHolding};
|
use token_core::{TokenDefinition, TokenHolding};
|
||||||
|
|
||||||
@ -67,6 +67,6 @@ pub fn mint(
|
|||||||
|
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(definition_post),
|
AccountPostState::new(definition_post),
|
||||||
AccountPostState::new_claimed_if_default(holding_post),
|
AccountPostState::new_claimed_if_default(holding_post, Claim::Authorized),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::AccountPostState,
|
program::{AccountPostState, Claim},
|
||||||
};
|
};
|
||||||
use token_core::{
|
use token_core::{
|
||||||
NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding, TokenMetadata,
|
NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding, TokenMetadata,
|
||||||
@ -42,8 +42,8 @@ pub fn new_fungible_definition(
|
|||||||
holding_target_account_post.data = Data::from(&token_holding);
|
holding_target_account_post.data = Data::from(&token_holding);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new_claimed(definition_target_account_post),
|
AccountPostState::new_claimed(definition_target_account_post, Claim::Authorized),
|
||||||
AccountPostState::new_claimed(holding_target_account_post),
|
AccountPostState::new_claimed(holding_target_account_post, Claim::Authorized),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,8 +119,8 @@ pub fn new_definition_with_metadata(
|
|||||||
metadata_target_account_post.data = Data::from(&token_metadata);
|
metadata_target_account_post.data = Data::from(&token_metadata);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new_claimed(definition_target_account_post),
|
AccountPostState::new_claimed(definition_target_account_post, Claim::Authorized),
|
||||||
AccountPostState::new_claimed(holding_target_account_post),
|
AccountPostState::new_claimed(holding_target_account_post, Claim::Authorized),
|
||||||
AccountPostState::new_claimed(metadata_target_account_post),
|
AccountPostState::new_claimed(metadata_target_account_post, Claim::Authorized),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::AccountPostState,
|
program::{AccountPostState, Claim},
|
||||||
};
|
};
|
||||||
use token_core::TokenHolding;
|
use token_core::TokenHolding;
|
||||||
|
|
||||||
@ -50,6 +50,6 @@ pub fn print_nft(
|
|||||||
|
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(master_account_post),
|
AccountPostState::new(master_account_post),
|
||||||
AccountPostState::new_claimed(printed_account_post),
|
AccountPostState::new_claimed(printed_account_post, Claim::Authorized),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,10 @@
|
|||||||
reason = "We don't care about it in tests"
|
reason = "We don't care about it in tests"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data};
|
use nssa_core::{
|
||||||
|
account::{Account, AccountId, AccountWithMetadata, Data},
|
||||||
|
program::Claim,
|
||||||
|
};
|
||||||
use token_core::{
|
use token_core::{
|
||||||
MetadataStandard, NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding,
|
MetadataStandard, NewTokenDefinition, NewTokenMetadata, TokenDefinition, TokenHolding,
|
||||||
};
|
};
|
||||||
@ -851,7 +854,7 @@ fn mint_uninit_holding_success() {
|
|||||||
*holding_post.account(),
|
*holding_post.account(),
|
||||||
AccountForTests::init_mint().account
|
AccountForTests::init_mint().account
|
||||||
);
|
);
|
||||||
assert!(holding_post.requires_claim());
|
assert_eq!(holding_post.required_claim(), Some(Claim::Authorized));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use nssa_core::{
|
use nssa_core::{
|
||||||
account::{Account, AccountWithMetadata, Data},
|
account::{Account, AccountWithMetadata, Data},
|
||||||
program::AccountPostState,
|
program::{AccountPostState, Claim},
|
||||||
};
|
};
|
||||||
use token_core::TokenHolding;
|
use token_core::TokenHolding;
|
||||||
|
|
||||||
@ -106,6 +106,6 @@ pub fn transfer(
|
|||||||
|
|
||||||
vec![
|
vec![
|
||||||
AccountPostState::new(sender_post),
|
AccountPostState::new(sender_post),
|
||||||
AccountPostState::new_claimed_if_default(recipient_post),
|
AccountPostState::new_claimed_if_default(recipient_post, Claim::Authorized),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ use mempool::{MemPool, MemPoolHandle};
|
|||||||
#[cfg(feature = "mock")]
|
#[cfg(feature = "mock")]
|
||||||
pub use mock::SequencerCoreWithMockClients;
|
pub use mock::SequencerCoreWithMockClients;
|
||||||
use nssa::V03State;
|
use nssa::V03State;
|
||||||
|
use nssa_core::{BlockId, Timestamp};
|
||||||
pub use storage::error::DbError;
|
pub use storage::error::DbError;
|
||||||
use testnet_initial_state::initial_state;
|
use testnet_initial_state::initial_state;
|
||||||
|
|
||||||
@ -165,14 +166,16 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
fn execute_check_transaction_on_state(
|
fn execute_check_transaction_on_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
tx: NSSATransaction,
|
tx: NSSATransaction,
|
||||||
|
block_id: BlockId,
|
||||||
|
timestamp: Timestamp,
|
||||||
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
||||||
match &tx {
|
match &tx {
|
||||||
NSSATransaction::Public(tx) => self
|
NSSATransaction::Public(tx) => self
|
||||||
.state
|
.state
|
||||||
.transition_from_public_transaction(tx, self.next_block_id()),
|
.transition_from_public_transaction(tx, block_id, timestamp),
|
||||||
NSSATransaction::PrivacyPreserving(tx) => self
|
NSSATransaction::PrivacyPreserving(tx) => self
|
||||||
.state
|
.state
|
||||||
.transition_from_privacy_preserving_transaction(tx, self.next_block_id()),
|
.transition_from_privacy_preserving_transaction(tx, block_id, timestamp),
|
||||||
NSSATransaction::ProgramDeployment(tx) => self
|
NSSATransaction::ProgramDeployment(tx) => self
|
||||||
.state
|
.state
|
||||||
.transition_from_program_deployment_transaction(tx),
|
.transition_from_program_deployment_transaction(tx),
|
||||||
@ -218,7 +221,7 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
.latest_block_meta()
|
.latest_block_meta()
|
||||||
.context("Failed to get latest block meta from store")?;
|
.context("Failed to get latest block meta from store")?;
|
||||||
|
|
||||||
let curr_time = u64::try_from(chrono::Utc::now().timestamp_millis())
|
let new_block_timestamp = u64::try_from(chrono::Utc::now().timestamp_millis())
|
||||||
.expect("Timestamp must be positive");
|
.expect("Timestamp must be positive");
|
||||||
|
|
||||||
while let Some(tx) = self.mempool.pop() {
|
while let Some(tx) = self.mempool.pop() {
|
||||||
@ -231,7 +234,7 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
block_id: new_block_height,
|
block_id: new_block_height,
|
||||||
transactions: temp_valid_transactions,
|
transactions: temp_valid_transactions,
|
||||||
prev_block_hash: latest_block_meta.hash,
|
prev_block_hash: latest_block_meta.hash,
|
||||||
timestamp: curr_time,
|
timestamp: new_block_timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_size = borsh::to_vec(&temp_hashable_data)
|
let block_size = borsh::to_vec(&temp_hashable_data)
|
||||||
@ -249,7 +252,8 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.execute_check_transaction_on_state(tx) {
|
match self.execute_check_transaction_on_state(tx, new_block_height, new_block_timestamp)
|
||||||
|
{
|
||||||
Ok(valid_tx) => {
|
Ok(valid_tx) => {
|
||||||
valid_transactions.push(valid_tx);
|
valid_transactions.push(valid_tx);
|
||||||
|
|
||||||
@ -272,7 +276,7 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
block_id: new_block_height,
|
block_id: new_block_height,
|
||||||
transactions: valid_transactions,
|
transactions: valid_transactions,
|
||||||
prev_block_hash: latest_block_meta.hash,
|
prev_block_hash: latest_block_meta.hash,
|
||||||
timestamp: curr_time,
|
timestamp: new_block_timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
let block = hashable_data
|
let block = hashable_data
|
||||||
@ -520,7 +524,7 @@ mod tests {
|
|||||||
let tx = tx.transaction_stateless_check().unwrap();
|
let tx = tx.transaction_stateless_check().unwrap();
|
||||||
|
|
||||||
// Signature is not from sender. Execution fails
|
// Signature is not from sender. Execution fails
|
||||||
let result = sequencer.execute_check_transaction_on_state(tx);
|
let result = sequencer.execute_check_transaction_on_state(tx, 0, 0);
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
result,
|
result,
|
||||||
@ -546,7 +550,7 @@ mod tests {
|
|||||||
// Passed pre-check
|
// Passed pre-check
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let result = sequencer.execute_check_transaction_on_state(result.unwrap());
|
let result = sequencer.execute_check_transaction_on_state(result.unwrap(), 0, 0);
|
||||||
let is_failed_at_balance_mismatch = matches!(
|
let is_failed_at_balance_mismatch = matches!(
|
||||||
result.err().unwrap(),
|
result.err().unwrap(),
|
||||||
nssa::error::NssaError::ProgramExecutionFailed(_)
|
nssa::error::NssaError::ProgramExecutionFailed(_)
|
||||||
@ -568,7 +572,9 @@ mod tests {
|
|||||||
acc1, 0, acc2, 100, &sign_key1,
|
acc1, 0, acc2, 100, &sign_key1,
|
||||||
);
|
);
|
||||||
|
|
||||||
sequencer.execute_check_transaction_on_state(tx).unwrap();
|
sequencer
|
||||||
|
.execute_check_transaction_on_state(tx, 0, 0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let bal_from = sequencer.state.get_account_by_id(acc1).balance;
|
let bal_from = sequencer.state.get_account_by_id(acc1).balance;
|
||||||
let bal_to = sequencer.state.get_account_by_id(acc2).balance;
|
let bal_to = sequencer.state.get_account_by_id(acc2).balance;
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
//! Reexports of types used by sequencer rpc specification.
|
//! Reexports of types used by sequencer rpc specification.
|
||||||
|
|
||||||
pub use common::{
|
pub use common::{HashType, block::Block, transaction::NSSATransaction};
|
||||||
HashType,
|
|
||||||
block::{Block, BlockId},
|
|
||||||
transaction::NSSATransaction,
|
|
||||||
};
|
|
||||||
pub use nssa::{Account, AccountId, ProgramId};
|
pub use nssa::{Account, AccountId, ProgramId};
|
||||||
pub use nssa_core::{Commitment, MembershipProof, account::Nonce};
|
pub use nssa_core::{BlockId, Commitment, MembershipProof, account::Nonce};
|
||||||
|
|||||||
@ -188,7 +188,11 @@ impl RocksDBIO {
|
|||||||
"transaction pre check failed with err {err:?}"
|
"transaction pre check failed with err {err:?}"
|
||||||
))
|
))
|
||||||
})?
|
})?
|
||||||
.execute_check_on_state(&mut breakpoint, block.header.block_id)
|
.execute_check_on_state(
|
||||||
|
&mut breakpoint,
|
||||||
|
block.header.block_id,
|
||||||
|
block.header.timestamp,
|
||||||
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
DbError::db_interaction_error(format!(
|
DbError::db_interaction_error(format!(
|
||||||
"transaction execution failed with err {err:?}"
|
"transaction execution failed with err {err:?}"
|
||||||
|
|||||||
@ -38,7 +38,7 @@ fn main() {
|
|||||||
program_id: auth_transfer_id,
|
program_id: auth_transfer_id,
|
||||||
instruction_data: instruction_data.clone(),
|
instruction_data: instruction_data.clone(),
|
||||||
pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], /* <- Account order permutation here */
|
pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], /* <- Account order permutation here */
|
||||||
pda_seeds: pda_seed.iter().cloned().collect(),
|
pda_seeds: pda_seed.iter().copied().collect(),
|
||||||
};
|
};
|
||||||
chained_calls.push(new_chained_call);
|
chained_calls.push(new_chained_call);
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = (Option<Vec<u8>>, bool);
|
type Instruction = (Option<Vec<u8>>, bool);
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ fn main() {
|
|||||||
|
|
||||||
// Claim or not based on the boolean flag
|
// Claim or not based on the boolean flag
|
||||||
let post_state = if should_claim {
|
let post_state = if should_claim {
|
||||||
AccountPostState::new_claimed(account_post)
|
AccountPostState::new_claimed(account_post, Claim::Authorized)
|
||||||
} else {
|
} else {
|
||||||
AccountPostState::new(account_post)
|
AccountPostState::new(account_post)
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = ();
|
type Instruction = ();
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ fn main() {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let account_post = AccountPostState::new_claimed(pre.account.clone());
|
let account_post = AccountPostState::new_claimed(pre.account.clone(), Claim::Authorized);
|
||||||
|
|
||||||
ProgramOutput::new(instruction_words, vec![pre], vec![account_post]).write();
|
ProgramOutput::new(instruction_words, vec![pre], vec![account_post]).write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use nssa_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_nssa_inputs};
|
use nssa_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_nssa_inputs};
|
||||||
|
|
||||||
type Instruction = Vec<u8>;
|
type Instruction = Vec<u8>;
|
||||||
|
|
||||||
@ -25,7 +25,10 @@ fn main() {
|
|||||||
ProgramOutput::new(
|
ProgramOutput::new(
|
||||||
instruction_words,
|
instruction_words,
|
||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new_claimed(account_post)],
|
vec![AccountPostState::new_claimed(
|
||||||
|
account_post,
|
||||||
|
Claim::Authorized,
|
||||||
|
)],
|
||||||
)
|
)
|
||||||
.write();
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
AccountPostState, ProgramInput, ProgramOutput, ValidityWindow, read_nssa_inputs,
|
AccountPostState, BlockValidityWindow, ProgramInput, ProgramOutput, TimestampValidityWindow,
|
||||||
|
read_nssa_inputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Instruction = ValidityWindow;
|
type Instruction = (BlockValidityWindow, TimestampValidityWindow);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let (
|
let (
|
||||||
ProgramInput {
|
ProgramInput {
|
||||||
pre_states,
|
pre_states,
|
||||||
instruction: validity_window,
|
instruction: (block_validity_window, timestamp_validity_window),
|
||||||
},
|
},
|
||||||
instruction_words,
|
instruction_words,
|
||||||
) = read_nssa_inputs::<Instruction>();
|
) = read_nssa_inputs::<Instruction>();
|
||||||
@ -24,6 +25,7 @@ fn main() {
|
|||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new(post)],
|
vec![AccountPostState::new(post)],
|
||||||
)
|
)
|
||||||
.with_validity_window(validity_window)
|
.with_block_validity_window(block_validity_window)
|
||||||
|
.with_timestamp_validity_window(timestamp_validity_window)
|
||||||
.write();
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
use nssa_core::program::{
|
use nssa_core::program::{
|
||||||
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, ValidityWindow,
|
AccountPostState, BlockValidityWindow, ChainedCall, ProgramId, ProgramInput, ProgramOutput,
|
||||||
read_nssa_inputs,
|
TimestampValidityWindow, read_nssa_inputs,
|
||||||
};
|
};
|
||||||
use risc0_zkvm::serde::to_vec;
|
use risc0_zkvm::serde::to_vec;
|
||||||
|
|
||||||
/// A program that sets a validity window on its output and chains to another program with a
|
/// A program that sets a block validity window on its output and chains to another program with a
|
||||||
/// potentially different validity window.
|
/// potentially different block validity window.
|
||||||
///
|
///
|
||||||
/// Instruction: (`window`, `chained_program_id`, `chained_window`)
|
/// Instruction: (`window`, `chained_program_id`, `chained_window`)
|
||||||
/// The initial output uses `window` and chains to `chained_program_id` with `chained_window`.
|
/// The initial output uses `window` and chains to `chained_program_id` with `chained_window`.
|
||||||
type Instruction = (ValidityWindow, ProgramId, ValidityWindow);
|
/// The chained program (`validity_window`) expects `(BlockValidityWindow, TimestampValidityWindow)`
|
||||||
|
/// so an unbounded timestamp window is appended automatically.
|
||||||
|
type Instruction = (BlockValidityWindow, ProgramId, BlockValidityWindow);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let (
|
let (
|
||||||
ProgramInput {
|
ProgramInput {
|
||||||
pre_states,
|
pre_states,
|
||||||
instruction: (validity_window, chained_program_id, chained_validity_window),
|
instruction: (block_validity_window, chained_program_id, chained_block_validity_window),
|
||||||
},
|
},
|
||||||
instruction_words,
|
instruction_words,
|
||||||
) = read_nssa_inputs::<Instruction>();
|
) = read_nssa_inputs::<Instruction>();
|
||||||
@ -23,7 +25,11 @@ fn main() {
|
|||||||
let [pre] = <[_; 1]>::try_from(pre_states.clone()).expect("Expected exactly one pre state");
|
let [pre] = <[_; 1]>::try_from(pre_states.clone()).expect("Expected exactly one pre state");
|
||||||
let post = pre.account.clone();
|
let post = pre.account.clone();
|
||||||
|
|
||||||
let chained_instruction = to_vec(&chained_validity_window).unwrap();
|
let chained_instruction = to_vec(&(
|
||||||
|
chained_block_validity_window,
|
||||||
|
TimestampValidityWindow::new_unbounded(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
let chained_call = ChainedCall {
|
let chained_call = ChainedCall {
|
||||||
program_id: chained_program_id,
|
program_id: chained_program_id,
|
||||||
instruction_data: chained_instruction,
|
instruction_data: chained_instruction,
|
||||||
@ -36,7 +42,7 @@ fn main() {
|
|||||||
vec![pre],
|
vec![pre],
|
||||||
vec![AccountPostState::new(post)],
|
vec![AccountPostState::new(post)],
|
||||||
)
|
)
|
||||||
.with_validity_window(validity_window)
|
.with_block_validity_window(block_validity_window)
|
||||||
.with_chained_calls(vec![chained_call])
|
.with_chained_calls(vec![chained_call])
|
||||||
.write();
|
.write();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,18 +58,21 @@ impl Amm<'_> {
|
|||||||
user_holding_lp,
|
user_holding_lp,
|
||||||
];
|
];
|
||||||
|
|
||||||
let nonces = self
|
let mut nonces = self
|
||||||
.0
|
.0
|
||||||
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
|
.get_accounts_nonces(vec![user_holding_a, user_holding_b])
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
|
||||||
|
let mut private_keys = Vec::new();
|
||||||
|
|
||||||
let signing_key_a = self
|
let signing_key_a = self
|
||||||
.0
|
.0
|
||||||
.storage
|
.storage
|
||||||
.user_data
|
.user_data
|
||||||
.get_pub_account_signing_key(user_holding_a)
|
.get_pub_account_signing_key(user_holding_a)
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||||
|
private_keys.push(signing_key_a);
|
||||||
|
|
||||||
let signing_key_b = self
|
let signing_key_b = self
|
||||||
.0
|
.0
|
||||||
@ -77,6 +80,26 @@ impl Amm<'_> {
|
|||||||
.user_data
|
.user_data
|
||||||
.get_pub_account_signing_key(user_holding_b)
|
.get_pub_account_signing_key(user_holding_b)
|
||||||
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||||
|
private_keys.push(signing_key_b);
|
||||||
|
|
||||||
|
if let Some(signing_key_lp) = self
|
||||||
|
.0
|
||||||
|
.storage
|
||||||
|
.user_data
|
||||||
|
.get_pub_account_signing_key(user_holding_lp)
|
||||||
|
{
|
||||||
|
private_keys.push(signing_key_lp);
|
||||||
|
let lp_nonces = self
|
||||||
|
.0
|
||||||
|
.get_accounts_nonces(vec![user_holding_lp])
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
nonces.extend(lp_nonces);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Liquidity pool tokens receiver's account ({user_holding_lp}) private key not found in wallet. Proceeding with only liquidity provider's keys."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
program.id(),
|
program.id(),
|
||||||
@ -86,10 +109,8 @@ impl Amm<'_> {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(
|
let witness_set =
|
||||||
&message,
|
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys);
|
||||||
&[signing_key_a, signing_key_b],
|
|
||||||
);
|
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
|||||||
@ -23,24 +23,40 @@ impl NativeTokenTransfer<'_> {
|
|||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
|
||||||
if balance >= balance_to_move {
|
if balance >= balance_to_move {
|
||||||
let nonces = self
|
let account_ids = vec![from, to];
|
||||||
|
let program_id = Program::authenticated_transfer_program().id();
|
||||||
|
|
||||||
|
let mut nonces = self
|
||||||
.0
|
.0
|
||||||
.get_accounts_nonces(vec![from])
|
.get_accounts_nonces(vec![from])
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
|
||||||
let account_ids = vec![from, to];
|
let mut private_keys = Vec::new();
|
||||||
let program_id = Program::authenticated_transfer_program().id();
|
let from_signing_key = self.0.storage.user_data.get_pub_account_signing_key(from);
|
||||||
let message =
|
let Some(from_signing_key) = from_signing_key else {
|
||||||
Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap();
|
|
||||||
|
|
||||||
let signing_key = self.0.storage.user_data.get_pub_account_signing_key(from);
|
|
||||||
|
|
||||||
let Some(signing_key) = signing_key else {
|
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
return Err(ExecutionFailureKind::KeyNotFoundError);
|
||||||
};
|
};
|
||||||
|
private_keys.push(from_signing_key);
|
||||||
|
|
||||||
let witness_set = WitnessSet::for_message(&message, &[signing_key]);
|
let to_signing_key = self.0.storage.user_data.get_pub_account_signing_key(to);
|
||||||
|
if let Some(to_signing_key) = to_signing_key {
|
||||||
|
private_keys.push(to_signing_key);
|
||||||
|
let to_nonces = self
|
||||||
|
.0
|
||||||
|
.get_accounts_nonces(vec![to])
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
nonces.extend(to_nonces);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Receiver's account ({to}) private key not found in wallet. Proceeding with only sender's key."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let message =
|
||||||
|
Message::try_new(program_id, account_ids, nonces, balance_to_move).unwrap();
|
||||||
|
let witness_set = WitnessSet::for_message(&message, &private_keys);
|
||||||
|
|
||||||
let tx = PublicTransaction::new(message, witness_set);
|
let tx = PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
|||||||
@ -19,15 +19,36 @@ impl Token<'_> {
|
|||||||
let account_ids = vec![definition_account_id, supply_account_id];
|
let account_ids = vec![definition_account_id, supply_account_id];
|
||||||
let program_id = nssa::program::Program::token().id();
|
let program_id = nssa::program::Program::token().id();
|
||||||
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
let instruction = Instruction::NewFungibleDefinition { name, total_supply };
|
||||||
|
let nonces = self
|
||||||
|
.0
|
||||||
|
.get_accounts_nonces(account_ids.clone())
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
program_id,
|
program_id,
|
||||||
account_ids,
|
account_ids,
|
||||||
vec![],
|
nonces,
|
||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
|
let def_private_key = self
|
||||||
|
.0
|
||||||
|
.storage
|
||||||
|
.user_data
|
||||||
|
.get_pub_account_signing_key(definition_account_id)
|
||||||
|
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||||
|
let supply_private_key = self
|
||||||
|
.0
|
||||||
|
.storage
|
||||||
|
.user_data
|
||||||
|
.get_pub_account_signing_key(supply_account_id)
|
||||||
|
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||||
|
|
||||||
|
let witness_set = nssa::public_transaction::WitnessSet::for_message(
|
||||||
|
&message,
|
||||||
|
&[def_private_key, supply_private_key],
|
||||||
|
);
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
@ -138,11 +159,40 @@ impl Token<'_> {
|
|||||||
let instruction = Instruction::Transfer {
|
let instruction = Instruction::Transfer {
|
||||||
amount_to_transfer: amount,
|
amount_to_transfer: amount,
|
||||||
};
|
};
|
||||||
let nonces = self
|
let mut nonces = self
|
||||||
.0
|
.0
|
||||||
.get_accounts_nonces(vec![sender_account_id])
|
.get_accounts_nonces(vec![sender_account_id])
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
|
||||||
|
let mut private_keys = Vec::new();
|
||||||
|
let sender_sk = self
|
||||||
|
.0
|
||||||
|
.storage
|
||||||
|
.user_data
|
||||||
|
.get_pub_account_signing_key(sender_account_id)
|
||||||
|
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||||
|
private_keys.push(sender_sk);
|
||||||
|
|
||||||
|
if let Some(recipient_sk) = self
|
||||||
|
.0
|
||||||
|
.storage
|
||||||
|
.user_data
|
||||||
|
.get_pub_account_signing_key(recipient_account_id)
|
||||||
|
{
|
||||||
|
private_keys.push(recipient_sk);
|
||||||
|
let recipient_nonces = self
|
||||||
|
.0
|
||||||
|
.get_accounts_nonces(vec![recipient_account_id])
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
nonces.extend(recipient_nonces);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Receiver's account ({recipient_account_id}) private key not found in wallet. Proceeding with only sender's key."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
program_id,
|
program_id,
|
||||||
account_ids,
|
account_ids,
|
||||||
@ -150,17 +200,8 @@ impl Token<'_> {
|
|||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let Some(signing_key) = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.user_data
|
|
||||||
.get_pub_account_signing_key(sender_account_id)
|
|
||||||
else {
|
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
|
||||||
};
|
|
||||||
let witness_set =
|
let witness_set =
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys);
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
@ -477,11 +518,40 @@ impl Token<'_> {
|
|||||||
amount_to_mint: amount,
|
amount_to_mint: amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
let nonces = self
|
let mut nonces = self
|
||||||
.0
|
.0
|
||||||
.get_accounts_nonces(vec![definition_account_id])
|
.get_accounts_nonces(vec![definition_account_id])
|
||||||
.await
|
.await
|
||||||
.map_err(ExecutionFailureKind::SequencerError)?;
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
|
||||||
|
let mut private_keys = Vec::new();
|
||||||
|
let definition_sk = self
|
||||||
|
.0
|
||||||
|
.storage
|
||||||
|
.user_data
|
||||||
|
.get_pub_account_signing_key(definition_account_id)
|
||||||
|
.ok_or(ExecutionFailureKind::KeyNotFoundError)?;
|
||||||
|
private_keys.push(definition_sk);
|
||||||
|
|
||||||
|
if let Some(holder_sk) = self
|
||||||
|
.0
|
||||||
|
.storage
|
||||||
|
.user_data
|
||||||
|
.get_pub_account_signing_key(holder_account_id)
|
||||||
|
{
|
||||||
|
private_keys.push(holder_sk);
|
||||||
|
let recipient_nonces = self
|
||||||
|
.0
|
||||||
|
.get_accounts_nonces(vec![holder_account_id])
|
||||||
|
.await
|
||||||
|
.map_err(ExecutionFailureKind::SequencerError)?;
|
||||||
|
nonces.extend(recipient_nonces);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"Holder's account ({holder_account_id}) private key not found in wallet. Proceeding with only definition's key."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let message = nssa::public_transaction::Message::try_new(
|
let message = nssa::public_transaction::Message::try_new(
|
||||||
Program::token().id(),
|
Program::token().id(),
|
||||||
account_ids,
|
account_ids,
|
||||||
@ -489,17 +559,8 @@ impl Token<'_> {
|
|||||||
instruction,
|
instruction,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let Some(signing_key) = self
|
|
||||||
.0
|
|
||||||
.storage
|
|
||||||
.user_data
|
|
||||||
.get_pub_account_signing_key(definition_account_id)
|
|
||||||
else {
|
|
||||||
return Err(ExecutionFailureKind::KeyNotFoundError);
|
|
||||||
};
|
|
||||||
let witness_set =
|
let witness_set =
|
||||||
nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]);
|
nssa::public_transaction::WitnessSet::for_message(&message, &private_keys);
|
||||||
|
|
||||||
let tx = nssa::PublicTransaction::new(message, witness_set);
|
let tx = nssa::PublicTransaction::new(message, witness_set);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user