Merge 08bae6fa82d4a8249e55657bd35f388f7e25f347 into d67269e6668cc2dcc9675358e244fcdcfe813031

This commit is contained in:
jonesmarvin8 2026-04-09 22:13:47 +00:00 committed by GitHub
commit d986c7cd89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
84 changed files with 1557 additions and 448 deletions

3
Cargo.lock generated
View File

@ -5297,7 +5297,9 @@ dependencies = [
"bytemuck",
"bytesize",
"chacha20",
"hex",
"k256",
"rand 0.8.5",
"risc0-zkvm",
"serde",
"serde_json",
@ -7612,6 +7614,7 @@ dependencies = [
"borsh",
"common",
"nssa",
"nssa_core",
"rocksdb",
"tempfile",
"thiserror 2.0.18",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -35,7 +35,7 @@ pub struct BlockHeader {
pub prev_block_hash: BlockHash,
pub hash: BlockHash,
pub timestamp: Timestamp,
pub signature: nssa::Signature,
pub signature: nssa_core::Signature,
}
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
@ -82,11 +82,11 @@ impl HashableBlockData {
#[must_use]
pub fn into_pending_block(
self,
signing_key: &nssa::PrivateKey,
signing_key: &nssa_core::PrivateKey,
bedrock_parent_id: MantleMsgId,
) -> Block {
let data_bytes = borsh::to_vec(&self).unwrap();
let signature = nssa::Signature::new(signing_key, &data_bytes);
let signature = nssa_core::Signature::new(signing_key, &data_bytes);
let hash = OwnHasher::hash(&data_bytes);
Block {
header: BlockHeader {

View File

@ -9,8 +9,8 @@ use crate::{
// Helpers
#[must_use]
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([37; 32]).unwrap()
pub fn sequencer_sign_key_for_testing() -> nssa_core::PrivateKey {
nssa_core::PrivateKey::try_new([37; 32]).unwrap()
}
// Dummy producers
@ -51,7 +51,7 @@ pub fn produce_dummy_empty_transaction() -> NSSATransaction {
instruction_data,
)
.unwrap();
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
let private_key = nssa_core::PrivateKey::try_new([1; 32]).unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]);
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
@ -65,7 +65,7 @@ pub fn create_transaction_native_token_transfer(
nonce: u128,
to: AccountId,
balance_to_move: u128,
signing_key: &nssa::PrivateKey,
signing_key: &nssa_core::PrivateKey,
) -> NSSATransaction {
let account_ids = vec![from, to];
let nonces = vec![nonce.into()];

View File

@ -52,7 +52,7 @@ The derivation works as follows:
```
seed = SHA256(owner_id || definition_id)
ata_address = AccountId::from((ata_program_id, seed))
ata_address = AccountId::public_account_id((ata_program_id, seed))
```
Because the computation is pure, anyone who knows the owner and definition can reproduce the exact same ATA address — no network call required.

View File

@ -166,7 +166,8 @@ impl IndexerStore {
#[cfg(test)]
mod tests {
use nssa::{AccountId, PublicKey};
use nssa::AccountId;
use nssa_core::PublicKey;
use tempfile::tempdir;
use super::*;
@ -175,20 +176,20 @@ mod tests {
common::test_utils::produce_dummy_block(1, None, vec![])
}
fn acc1_sign_key() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([1; 32]).unwrap()
fn acc1_sign_key() -> nssa_core::PrivateKey {
nssa_core::PrivateKey::try_new([1; 32]).unwrap()
}
fn acc2_sign_key() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([2; 32]).unwrap()
fn acc2_sign_key() -> nssa_core::PrivateKey {
nssa_core::PrivateKey::try_new([2; 32]).unwrap()
}
fn acc1() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&acc1_sign_key()))
AccountId::public_account_id(&PublicKey::new_from_private_key(&acc1_sign_key()))
}
fn acc2() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&acc2_sign_key()))
AccountId::public_account_id(&PublicKey::new_from_private_key(&acc2_sign_key()))
}
#[test]

View File

@ -11,7 +11,8 @@ use logos_blockchain_core::mantle::{
Op, SignedMantleTx,
ops::channel::{ChannelId, inscribe::InscriptionOp},
};
use nssa::V03State;
use nssa::{AccountId, V03State};
use nssa_core::account::Identifier;
use testnet_initial_state::initial_state_testnet;
use crate::{block_store::IndexerStore, config::IndexerConfig};
@ -53,7 +54,7 @@ impl IndexerCore {
// because it will be overwritten by sequencer.
// Therefore:
// ToDo: remove key from indexer config, use some default.
let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap();
let signing_key = nssa_core::PrivateKey::try_new(config.signing_key).unwrap();
let channel_genesis_msg_id = [0; 32];
let genesis_block = hashable_data.into_pending_block(&signing_key, channel_genesis_msg_id);
@ -71,7 +72,10 @@ impl IndexerCore {
acc.program_owner =
nssa::program::Program::authenticated_transfer_program().id();
nssa_core::Commitment::new(npk, &acc)
nssa_core::Commitment::new(
&AccountId::private_account_id(npk, Identifier(0_u128)),
&acc,
)
})
.collect()
});

View File

@ -165,28 +165,28 @@ impl From<EphemeralPublicKey> for nssa_core::encryption::EphemeralPublicKey {
// Signature and PublicKey conversions
// ============================================================================
impl From<nssa::Signature> for Signature {
fn from(value: nssa::Signature) -> Self {
let nssa::Signature { value } = value;
impl From<nssa_core::Signature> for Signature {
fn from(value: nssa_core::Signature) -> Self {
let nssa_core::Signature { value } = value;
Self(value)
}
}
impl From<Signature> for nssa::Signature {
impl From<Signature> for nssa_core::Signature {
fn from(value: Signature) -> Self {
let Signature(sig_value) = value;
Self { value: sig_value }
}
}
impl From<nssa::PublicKey> for PublicKey {
fn from(value: nssa::PublicKey) -> Self {
impl From<nssa_core::PublicKey> for PublicKey {
fn from(value: nssa_core::PublicKey) -> Self {
Self(*value.value())
}
}
impl TryFrom<PublicKey> for nssa::PublicKey {
type Error = nssa::error::NssaError;
impl TryFrom<PublicKey> for nssa_core::PublicKey {
type Error = nssa_core::error::NssaCoreError;
fn try_from(value: PublicKey) -> Result<Self, Self::Error> {
Self::try_new(value.0)

View File

@ -3,9 +3,13 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration};
use anyhow::{Context as _, Result};
use bytesize::ByteSize;
use indexer_service::{BackoffConfig, ChannelId, ClientConfig, IndexerConfig};
use key_protocol::key_management::KeyChain;
use nssa::{Account, AccountId, PrivateKey, PublicKey};
use nssa_core::{account::Data, program::DEFAULT_PROGRAM_ID};
use key_protocol::{key_management::KeyChain, key_protocol_core::PrivateBundle};
use nssa::{Account, AccountId};
use nssa_core::{
PrivateKey, PublicKey,
account::{Data, Identifier},
program::DEFAULT_PROGRAM_ID,
};
use sequencer_core::config::{BedrockConfig, SequencerConfig};
use testnet_initial_state::{
PrivateAccountPrivateInitialData, PrivateAccountPublicInitialData,
@ -36,7 +40,7 @@ impl Default for SequencerPartialConfig {
pub struct InitialData {
pub public_accounts: Vec<(PrivateKey, u128)>,
pub private_accounts: Vec<(KeyChain, Account)>,
pub private_accounts: Vec<PrivateBundle>,
}
impl InitialData {
@ -45,11 +49,11 @@ impl InitialData {
let mut public_alice_private_key = PrivateKey::new_os_random();
let mut public_alice_public_key =
PublicKey::new_from_private_key(&public_alice_private_key);
let mut public_alice_account_id = AccountId::from(&public_alice_public_key);
let mut public_alice_account_id = AccountId::public_account_id(&public_alice_public_key);
let mut public_bob_private_key = PrivateKey::new_os_random();
let mut public_bob_public_key = PublicKey::new_from_private_key(&public_bob_private_key);
let mut public_bob_account_id = AccountId::from(&public_bob_public_key);
let mut public_bob_account_id = AccountId::public_account_id(&public_bob_public_key);
// Ensure consistent ordering
if public_alice_account_id > public_bob_account_id {
@ -59,12 +63,18 @@ impl InitialData {
}
let mut private_charlie_key_chain = KeyChain::new_os_random();
let mut private_charlie_account_id =
AccountId::from(&private_charlie_key_chain.nullifier_public_key);
let mut private_charlie_identifier = Identifier::new_os_random();
let mut private_charlie_account_id = AccountId::private_account_id(
&private_charlie_key_chain.nullifier_public_key,
private_charlie_identifier,
);
let mut private_david_key_chain = KeyChain::new_os_random();
let mut private_david_account_id =
AccountId::from(&private_david_key_chain.nullifier_public_key);
let mut private_david_identifier = Identifier::new_os_random();
let mut private_david_account_id = AccountId::private_account_id(
&private_david_key_chain.nullifier_public_key,
private_david_identifier,
);
// Ensure consistent ordering
if private_charlie_account_id > private_david_account_id {
@ -73,6 +83,10 @@ impl InitialData {
&mut private_charlie_account_id,
&mut private_david_account_id,
);
std::mem::swap(
&mut private_charlie_identifier,
&mut private_david_identifier,
);
}
Self {
@ -81,24 +95,26 @@ impl InitialData {
(public_bob_private_key, 20_000),
],
private_accounts: vec![
(
private_charlie_key_chain,
Account {
PrivateBundle {
key_chain: private_charlie_key_chain,
identifier: private_charlie_identifier,
account: Account {
balance: 10_000,
data: Data::default(),
program_owner: DEFAULT_PROGRAM_ID,
nonce: 0_u128.into(),
},
),
(
private_david_key_chain,
Account {
},
PrivateBundle {
key_chain: private_david_key_chain,
identifier: private_david_identifier,
account: Account {
balance: 20_000,
data: Data::default(),
program_owner: DEFAULT_PROGRAM_ID,
nonce: 0_u128.into(),
},
),
},
],
}
}
@ -108,7 +124,7 @@ impl InitialData {
.iter()
.map(|(priv_key, balance)| {
let pub_key = PublicKey::new_from_private_key(priv_key);
let account_id = AccountId::from(&pub_key);
let account_id = AccountId::public_account_id(&pub_key);
PublicAccountPublicInitialData {
account_id,
balance: *balance,
@ -120,9 +136,10 @@ impl InitialData {
fn sequencer_initial_private_accounts(&self) -> Vec<PrivateAccountPublicInitialData> {
self.private_accounts
.iter()
.map(|(key_chain, account)| PrivateAccountPublicInitialData {
npk: key_chain.nullifier_public_key.clone(),
account: account.clone(),
.map(|bundle| PrivateAccountPublicInitialData {
npk: bundle.key_chain.nullifier_public_key.clone(),
identifier: bundle.identifier,
account: bundle.account.clone(),
})
.collect()
}
@ -132,18 +149,22 @@ impl InitialData {
.iter()
.map(|(priv_key, _)| {
let pub_key = PublicKey::new_from_private_key(priv_key);
let account_id = AccountId::from(&pub_key);
let account_id = AccountId::public_account_id(&pub_key);
InitialAccountData::Public(PublicAccountPrivateInitialData {
account_id,
pub_sign_key: priv_key.clone(),
})
})
.chain(self.private_accounts.iter().map(|(key_chain, account)| {
let account_id = AccountId::from(&key_chain.nullifier_public_key);
.chain(self.private_accounts.iter().map(|bundle| {
let account_id = AccountId::private_account_id(
&bundle.key_chain.nullifier_public_key,
bundle.identifier,
);
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
account_id,
account: account.clone(),
key_chain: key_chain.clone(),
account: bundle.account.clone(),
identifier: bundle.identifier,
key_chain: bundle.key_chain.clone(),
}))
}))
.collect()

View File

@ -49,7 +49,6 @@ async fn private_transfer_to_owned_account() -> Result<()> {
.get_private_account_commitment(to)
.context("Failed to get private account commitment for receiver")?;
assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await);
info!("Successfully transferred privately to owned account");
Ok(())
@ -75,26 +74,24 @@ async fn private_transfer_to_foreign_account() -> Result<()> {
});
let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?;
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else {
let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = result else {
anyhow::bail!("Expected PrivacyPreservingTransfer return value");
};
info!("Waiting for next block creation");
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let new_commitment1 = ctx
.wallet()
.get_private_account_commitment(from)
.context("Failed to get private account commitment for sender")?;
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await;
assert_eq!(tx.message.new_commitments[0], new_commitment1);
assert_eq!(tx.message.new_commitments.len(), 2);
for commitment in tx.message.new_commitments {
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
}
// info!("Waiting for next block creation");
// tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
//
// let new_commitment1 = ctx
// .wallet()
// .get_private_account_commitment(from)
// .context("Failed to get private account commitment for sender")?;
//
// let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await;
// assert_eq!(tx.message.new_commitments[0], new_commitment1);
//
// assert_eq!(tx.message.new_commitments.len(), 2);
// for commitment in tx.message.new_commitments {
// assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
// }
info!("Successfully transferred privately to foreign account");
Ok(())

View File

@ -259,16 +259,16 @@ async fn restore_keys_from_seed() -> Result<()> {
.expect("Acc 4 should be restored");
assert_eq!(
acc1.value.1.program_owner,
acc1.value.account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(
acc2.value.1.program_owner,
acc2.value.account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(acc1.value.1.balance, 100);
assert_eq!(acc2.value.1.balance, 101);
assert_eq!(acc1.value.account.balance, 100);
assert_eq!(acc2.value.account.balance, 101);
info!("Tree checks passed, testing restored accounts can transact");

View File

@ -18,17 +18,20 @@ use integration_tests::{
TestContext,
config::{InitialData, SequencerPartialConfig},
};
use key_protocol::key_management::{KeyChain, ephemeral_key_holder::EphemeralKeyHolder};
use key_protocol::{
key_management::{KeyChain, ephemeral_key_holder::EphemeralKeyHolder},
key_protocol_core::PrivateBundle,
};
use log::info;
use nssa::{
Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction,
Account, AccountId, PrivacyPreservingTransaction, PublicTransaction,
privacy_preserving_transaction::{self as pptx, circuit},
program::Program,
public_transaction as putx,
};
use nssa_core::{
MembershipProof, NullifierPublicKey,
account::{AccountWithMetadata, Nonce, data::Data},
MembershipProof, NullifierPublicKey, PrivateKey, PublicKey,
account::{AccountWithMetadata, Identifier, Nonce, data::Data},
encryption::ViewingPublicKey,
};
use sequencer_service_rpc::RpcClient as _;
@ -49,7 +52,7 @@ impl TpsTestManager {
private_key_bytes[..8].copy_from_slice(&i.to_le_bytes());
let private_key = PrivateKey::try_new(private_key_bytes).unwrap();
let public_key = PublicKey::new_from_private_key(&private_key);
let account_id = AccountId::from(&public_key);
let account_id = AccountId::public_account_id(&public_key);
(private_key, account_id)
})
.collect();
@ -107,6 +110,7 @@ impl TpsTestManager {
// Generate an initial commitment to be used with the privacy preserving transaction
// created with the `build_privacy_transaction` function.
let key_chain = KeyChain::new_os_random();
let identifier = Identifier::new_os_random();
let account = Account {
balance: 100,
nonce: Nonce(0xdead_beef),
@ -116,7 +120,11 @@ impl TpsTestManager {
InitialData {
public_accounts,
private_accounts: vec![(key_chain, account)],
private_accounts: vec![PrivateBundle {
key_chain,
identifier,
account,
}],
}
}
@ -212,6 +220,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
let sender_vsk = [99; 32];
let sender_vpk = ViewingPublicKey::from_scalar(sender_vsk);
let sender_npk = NullifierPublicKey::from(&sender_nsk);
let sender_id = AccountId::private_account_id(&sender_npk, Identifier(0_u128));
let sender_pre = AccountWithMetadata::new(
Account {
balance: 100,
@ -220,14 +229,18 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
data: Data::default(),
},
true,
AccountId::from(&sender_npk),
AccountId::private_account_id(&sender_npk, Identifier(0_u128)),
);
let recipient_nsk = [2; 32];
let recipient_vsk = [99; 32];
let recipient_vpk = ViewingPublicKey::from_scalar(recipient_vsk);
let recipient_npk = NullifierPublicKey::from(&recipient_nsk);
let recipient_pre =
AccountWithMetadata::new(Account::default(), false, AccountId::from(&recipient_npk));
let recipient_id = AccountId::private_account_id(&recipient_npk, Identifier(0_u128));
let recipient_pre = AccountWithMetadata::new(
Account::default(),
false,
AccountId::private_account_id(&recipient_npk, Identifier(0_u128)),
);
let eph_holder_from = EphemeralKeyHolder::new(&sender_npk);
let sender_ss = eph_holder_from.calculate_shared_secret_sender(&sender_vpk);
@ -249,11 +262,9 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
vec![sender_pre, recipient_pre],
Program::serialize_instruction(balance_to_move).unwrap(),
vec![1, 2],
vec![
(sender_npk.clone(), sender_ss),
(recipient_npk.clone(), recipient_ss),
],
vec![(sender_npk, sender_ss), (recipient_npk, recipient_ss)],
vec![sender_nsk],
vec![],
vec![Some(proof)],
&program.into(),
)
@ -262,8 +273,8 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
vec![],
vec![],
vec![
(sender_npk, sender_vpk, sender_epk),
(recipient_npk, recipient_vpk, recipient_epk),
(sender_id, sender_vpk, sender_epk),
(recipient_id, recipient_vpk, recipient_epk),
],
output,
)

View File

@ -21,8 +21,8 @@ use std::{
use anyhow::Result;
use integration_tests::{BlockingTestContext, TIME_TO_WAIT_FOR_BLOCK_SECONDS};
use log::info;
use nssa::{Account, AccountId, PrivateKey, PublicKey, program::Program};
use nssa_core::program::DEFAULT_PROGRAM_ID;
use nssa::{Account, AccountId, program::Program};
use nssa_core::{PrivateKey, PublicKey, account::Identifier, program::DEFAULT_PROGRAM_ID};
use tempfile::tempdir;
use wallet_ffi::{
FfiAccount, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey,
@ -332,7 +332,7 @@ fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> {
};
assert_eq!(
nssa::AccountId::from(&private_account_keys.npk()),
nssa::AccountId::private_account_id(&private_account_keys.npk(), Identifier(0_u128)),
out_private_account_id.into()
);
@ -607,7 +607,7 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> {
fn test_wallet_ffi_account_id_to_base58() -> Result<()> {
let private_key = PrivateKey::new_os_random();
let public_key = PublicKey::new_from_private_key(&private_key);
let account_id = AccountId::from(&public_key);
let account_id = AccountId::public_account_id(&public_key);
let ffi_bytes: FfiBytes32 = (&account_id).into();
let ptr = unsafe { wallet_ffi_account_id_to_base58(&raw const ffi_bytes) };
@ -626,7 +626,7 @@ fn test_wallet_ffi_account_id_to_base58() -> Result<()> {
fn wallet_ffi_base58_to_account_id() -> Result<()> {
let private_key = PrivateKey::new_os_random();
let public_key = PublicKey::new_from_private_key(&private_key);
let account_id = AccountId::from(&public_key);
let account_id = AccountId::public_account_id(&public_key);
let account_id_str = account_id.to_string();
let account_id_c_str = CString::new(account_id_str.clone())?;
let account_id: AccountId = unsafe {

View File

@ -1,5 +1,5 @@
use k256::{Scalar, elliptic_curve::PrimeField as _};
use nssa_core::{NullifierPublicKey, encryption::ViewingPublicKey};
use nssa_core::{NullifierPublicKey, account::Identifier, encryption::ViewingPublicKey};
use serde::{Deserialize, Serialize};
use crate::{
@ -13,7 +13,7 @@ use crate::{
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ChildKeysPrivate {
pub value: (KeyChain, nssa::Account),
pub value: PrivateBundle,
pub ccc: [u8; 32],
/// Can be [`None`] if root.
pub cci: Option<u32>,
@ -39,8 +39,8 @@ impl KeyNode for ChildKeysPrivate {
let vpk = ViewingPublicKey::from_scalar(vsk);
Self {
value: (
KeyChain {
value: PrivateBundle {
key_chain: KeyChain {
secret_spending_key: ssk,
nullifier_public_key: npk,
viewing_public_key: vpk,
@ -49,8 +49,9 @@ impl KeyNode for ChildKeysPrivate {
viewing_secret_key: vsk,
},
},
nssa::Account::default(),
),
identifier: Identifier::default(),
account: nssa::Account::default(),
},
ccc,
cci: None,
}
@ -58,11 +59,22 @@ impl KeyNode for ChildKeysPrivate {
fn nth_child(&self, cci: u32) -> Self {
#[expect(clippy::arithmetic_side_effects, reason = "TODO: fix later")]
let parent_pt =
Scalar::from_repr(self.value.0.private_key_holder.nullifier_secret_key.into())
.expect("Key generated as scalar, must be valid representation")
* Scalar::from_repr(self.value.0.private_key_holder.viewing_secret_key.into())
.expect("Key generated as scalar, must be valid representation");
let parent_pt = Scalar::from_repr(
self.value
.key_chain
.private_key_holder
.nullifier_secret_key
.into(),
)
.expect("Key generated as scalar, must be valid representation")
* Scalar::from_repr(
self.value
.key_chain
.private_key_holder
.viewing_secret_key
.into(),
)
.expect("Key generated as scalar, must be valid representation");
let mut input = vec![];
input.extend_from_slice(b"LEE_seed_priv");
@ -88,8 +100,8 @@ impl KeyNode for ChildKeysPrivate {
let vpk = ViewingPublicKey::from_scalar(vsk);
Self {
value: (
KeyChain {
value: PrivateBundle {
key_chain: KeyChain {
secret_spending_key: ssk,
nullifier_public_key: npk,
viewing_public_key: vpk,
@ -98,8 +110,9 @@ impl KeyNode for ChildKeysPrivate {
viewing_secret_key: vsk,
},
},
nssa::Account::default(),
),
identifier: Identifier(0_u128), // TODO: this Marvin
account: nssa::Account::default(),
},
ccc,
cci: Some(cci),
}
@ -114,17 +127,10 @@ impl KeyNode for ChildKeysPrivate {
}
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.value.0.nullifier_public_key)
}
}
#[expect(
clippy::single_char_lifetime_names,
reason = "TODO add meaningful name"
)]
impl<'a> From<&'a ChildKeysPrivate> for &'a (KeyChain, nssa::Account) {
fn from(value: &'a ChildKeysPrivate) -> Self {
&value.value
nssa::AccountId::private_account_id(
&self.value.key_chain.nullifier_public_key,
Identifier(0_u128),
)
}
}
@ -134,10 +140,7 @@ impl<'a> From<&'a ChildKeysPrivate> for &'a (KeyChain, nssa::Account) {
)]
impl<'a> From<&'a mut ChildKeysPrivate> for PrivateBundle {
fn from(value: &'a mut ChildKeysPrivate) -> Self {
Self {
key_chain: value.value.0.clone(),
account: value.value.1.clone(),
}
value.value.clone()
}
}
@ -189,12 +192,12 @@ mod tests {
80, 170, 66, 217, 79, 38, 80, 11, 74, 147, 123, 221, 159, 166,
];
assert!(expected_ssk == keys.value.0.secret_spending_key);
assert!(expected_ssk == keys.value.key_chain.secret_spending_key);
assert!(expected_ccc == keys.ccc);
assert!(expected_nsk == keys.value.0.private_key_holder.nullifier_secret_key);
assert!(expected_npk == keys.value.0.nullifier_public_key);
assert!(expected_vsk == keys.value.0.private_key_holder.viewing_secret_key);
assert!(expected_vpk_as_bytes == keys.value.0.viewing_public_key.to_bytes());
assert!(expected_nsk == keys.value.key_chain.private_key_holder.nullifier_secret_key);
assert!(expected_npk == keys.value.key_chain.nullifier_public_key);
assert!(expected_vsk == keys.value.key_chain.private_key_holder.viewing_secret_key);
assert!(expected_vpk_as_bytes == keys.value.key_chain.viewing_public_key.to_bytes());
}
#[test]
@ -233,9 +236,23 @@ mod tests {
];
assert!(expected_ccc == child_node.ccc);
assert!(expected_nsk == child_node.value.0.private_key_holder.nullifier_secret_key);
assert!(expected_npk == child_node.value.0.nullifier_public_key);
assert!(expected_vsk == child_node.value.0.private_key_holder.viewing_secret_key);
assert!(expected_vpk_as_bytes == child_node.value.0.viewing_public_key.to_bytes());
assert!(
expected_nsk
== child_node
.value
.key_chain
.private_key_holder
.nullifier_secret_key
);
assert!(expected_npk == child_node.value.key_chain.nullifier_public_key);
assert!(
expected_vsk
== child_node
.value
.key_chain
.private_key_holder
.viewing_secret_key
);
assert!(expected_vpk_as_bytes == child_node.value.key_chain.viewing_public_key.to_bytes());
}
}

View File

@ -5,8 +5,8 @@ use crate::key_management::key_tree::traits::KeyNode;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ChildKeysPublic {
pub csk: nssa::PrivateKey,
pub cpk: nssa::PublicKey,
pub csk: nssa_core::PrivateKey,
pub cpk: nssa_core::PublicKey,
pub ccc: [u8; 32],
/// Can be [`None`] if root.
pub cci: Option<u32>,
@ -41,14 +41,14 @@ impl KeyNode for ChildKeysPublic {
fn root(seed: [u8; 64]) -> Self {
let hash_value = hmac_sha512::HMAC::mac(seed, "LEE_master_pub");
let csk = nssa::PrivateKey::try_new(
let csk = nssa_core::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 ccc = *hash_value.last_chunk::<32>().unwrap();
let cpk = nssa::PublicKey::new_from_private_key(&csk);
let cpk = nssa_core::PublicKey::new_from_private_key(&csk);
Self {
csk,
@ -61,7 +61,7 @@ impl KeyNode for ChildKeysPublic {
fn nth_child(&self, cci: u32) -> Self {
let hash_value = self.compute_hash_value(cci);
let csk = nssa::PrivateKey::try_new({
let csk = nssa_core::PrivateKey::try_new({
let hash_value = hash_value
.first_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get first 32");
@ -80,7 +80,7 @@ impl KeyNode for ChildKeysPublic {
.last_chunk::<32>()
.expect("hash_value is 64 bytes, must be safe to get last 32");
let cpk = nssa::PublicKey::new_from_private_key(&csk);
let cpk = nssa_core::PublicKey::new_from_private_key(&csk);
Self {
csk,
@ -99,7 +99,7 @@ impl KeyNode for ChildKeysPublic {
}
fn account_id(&self) -> nssa::AccountId {
nssa::AccountId::from(&self.cpk)
nssa::AccountId::public_account_id(&self.cpk)
}
}
@ -107,7 +107,7 @@ impl KeyNode for ChildKeysPublic {
clippy::single_char_lifetime_names,
reason = "TODO add meaningful name"
)]
impl<'a> From<&'a ChildKeysPublic> for &'a nssa::PrivateKey {
impl<'a> From<&'a ChildKeysPublic> for &'a nssa_core::PrivateKey {
fn from(value: &'a ChildKeysPublic) -> Self {
&value.csk
}
@ -115,7 +115,7 @@ impl<'a> From<&'a ChildKeysPublic> for &'a nssa::PrivateKey {
#[cfg(test)]
mod tests {
use nssa::{PrivateKey, PublicKey};
use nssa_core::{PrivateKey, PublicKey};
use super::*;

View File

@ -212,7 +212,7 @@ impl KeyTree<ChildKeysPrivate> {
println!("Cleanup of tree at depth {i}");
for id in ChainIndex::chain_ids_at_depth(i) {
if let Some(node) = self.key_map.get(&id) {
if node.value.1 == nssa::Account::default() {
if node.value.account == nssa::Account::default() {
let addr = node.account_id();
self.remove(addr);
} else {
@ -478,25 +478,25 @@ mod tests {
.key_map
.get_mut(&ChainIndex::from_str("/1").unwrap())
.unwrap();
acc.value.1.balance = 2;
acc.value.account.balance = 2;
let acc = tree
.key_map
.get_mut(&ChainIndex::from_str("/2").unwrap())
.unwrap();
acc.value.1.balance = 3;
acc.value.account.balance = 3;
let acc = tree
.key_map
.get_mut(&ChainIndex::from_str("/0/1").unwrap())
.unwrap();
acc.value.1.balance = 5;
acc.value.account.balance = 5;
let acc = tree
.key_map
.get_mut(&ChainIndex::from_str("/1/0").unwrap())
.unwrap();
acc.value.1.balance = 6;
acc.value.account.balance = 6;
tree.cleanup_tree_remove_uninit_layered(10);
@ -518,15 +518,15 @@ mod tests {
assert_eq!(key_set, key_set_res);
let acc = &tree.key_map[&ChainIndex::from_str("/1").unwrap()];
assert_eq!(acc.value.1.balance, 2);
assert_eq!(acc.value.account.balance, 2);
let acc = &tree.key_map[&ChainIndex::from_str("/2").unwrap()];
assert_eq!(acc.value.1.balance, 3);
assert_eq!(acc.value.account.balance, 3);
let acc = &tree.key_map[&ChainIndex::from_str("/0/1").unwrap()];
assert_eq!(acc.value.1.balance, 5);
assert_eq!(acc.value.account.balance, 5);
let acc = &tree.key_map[&ChainIndex::from_str("/1/0").unwrap()];
assert_eq!(acc.value.1.balance, 6);
assert_eq!(acc.value.account.balance, 6);
}
}

View File

@ -125,11 +125,11 @@ mod tests {
let nullifier_public_key = utxo_secret_key_holder.generate_nullifier_public_key();
let viewing_public_key = utxo_secret_key_holder.generate_viewing_public_key();
let pub_account_signing_key = nssa::PrivateKey::new_os_random();
let pub_account_signing_key = nssa_core::PrivateKey::new_os_random();
let public_key = nssa::PublicKey::new_from_private_key(&pub_account_signing_key);
let public_key = nssa_core::PublicKey::new_from_private_key(&pub_account_signing_key);
let account = nssa::AccountId::from(&public_key);
let account = nssa::AccountId::public_account_id(&public_key);
println!("======Prerequisites======");
println!();
@ -178,7 +178,7 @@ mod tests {
.get_node(second_child_id)
.unwrap()
.value
.0
.key_chain
.clone()
}

View File

@ -2,6 +2,7 @@ use std::collections::BTreeMap;
use anyhow::Result;
use k256::AffinePoint;
use nssa_core::account::Identifier;
use serde::{Deserialize, Serialize};
use crate::key_management::{
@ -27,12 +28,13 @@ pub struct NSSAUserData {
/// TODO: eventually, this should have `sign_key: Option<PrivateKey>` and `pub_key: PublicKey`.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PublicBundle {
pub sign_key: nssa::PrivateKey,
pub sign_key: nssa_core::PrivateKey,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PrivateBundle {
pub key_chain: KeyChain,
pub identifier: Identifier,
pub account: nssa_core::account::Account,
}
@ -42,8 +44,8 @@ impl NSSAUserData {
) -> bool {
let mut check_res = true;
for (account_id, public_bundle) in accounts_keys_map {
let expected_account_id = nssa::AccountId::from(
&nssa::PublicKey::new_from_private_key(&public_bundle.sign_key),
let expected_account_id = nssa::AccountId::public_account_id(
&nssa_core::PublicKey::new_from_private_key(&public_bundle.sign_key),
);
if &expected_account_id != account_id {
println!("{expected_account_id}, {account_id}");
@ -58,7 +60,10 @@ impl NSSAUserData {
) -> bool {
let mut check_res = true;
for (account_id, bundle) in accounts_keys_map {
let expected_account_id = nssa::AccountId::from(&bundle.key_chain.nullifier_public_key);
let expected_account_id = nssa::AccountId::private_account_id(
&bundle.key_chain.nullifier_public_key,
bundle.identifier,
);
if expected_account_id != *account_id {
println!("{expected_account_id}, {account_id}");
check_res = false;
@ -117,7 +122,7 @@ impl NSSAUserData {
pub fn get_pub_account_signing_key(
&self,
account_id: nssa::AccountId,
) -> Option<&nssa::PrivateKey> {
) -> Option<&nssa_core::PrivateKey> {
self.default_pub_account_signing_keys
.get(&account_id)
.map(|bundle| &bundle.sign_key)
@ -153,8 +158,9 @@ impl NSSAUserData {
self.private_key_tree
.get_node(account_id)
.map(|child_keys_private| PrivateBundle {
key_chain: child_keys_private.value.0.clone(),
account: child_keys_private.value.1.clone(),
key_chain: child_keys_private.value.key_chain.clone(),
identifier: child_keys_private.value.identifier,
account: child_keys_private.value.account.clone(),
})
})
}

View File

@ -8,7 +8,7 @@ license = { workspace = true }
workspace = true
[dependencies]
nssa_core = { workspace = true, features = ["host"] }
nssa_core = { workspace = true, features = ["host", "test-utils"] }
clock_core.workspace = true
anyhow.workspace = true

View File

@ -12,10 +12,12 @@ risc0-zkvm.workspace = true
borsh.workspace = true
serde.workspace = true
serde_with.workspace = true
rand.workspace = true
thiserror.workspace = true
bytemuck.workspace = true
bytesize.workspace = true
base58.workspace = true
hex.workspace = true
k256 = { workspace = true, optional = true }
chacha20 = { version = "0.10" }
@ -23,5 +25,6 @@ chacha20 = { version = "0.10" }
serde_json.workspace = true
[features]
default = []
default = ["dep:k256"]
host = ["dep:k256"]
test-utils = ["dep:k256"]

View File

@ -6,16 +6,21 @@ use std::{
use base58::{FromBase58 as _, ToBase58 as _};
use borsh::{BorshDeserialize, BorshSerialize};
pub use data::Data;
use rand::{RngCore as _, rngs::OsRng};
use risc0_zkvm::sha::{Impl, Sha256 as _};
use serde::{Deserialize, Serialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use crate::{NullifierPublicKey, NullifierSecretKey, program::ProgramId};
use crate::{
EphemeralPublicKey, NullifierPublicKey, NullifierSecretKey, PublicKey, program::ProgramId,
};
pub mod data;
#[derive(Copy, Debug, Default, Clone, Eq, PartialEq)]
pub struct Nonce(pub u128);
#[derive(Copy, Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct Identifier(pub u128);
impl Nonce {
pub const fn public_account_nonce_increment(&mut self) {
@ -91,6 +96,30 @@ impl BorshDeserialize for Nonce {
pub type Balance = u128;
impl Identifier {
#[must_use]
pub fn private_identifier(epk: &EphemeralPublicKey, index: u8) -> Self {
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/Identifier/\x00";
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(PRIVATE_ACCOUNT_ID_PREFIX);
bytes.extend_from_slice(&epk.0);
bytes.extend_from_slice(&[index]);
let mut value = [0_u8; 16];
value.copy_from_slice(&Impl::hash_bytes(&bytes).as_bytes()[0..16]);
Self(u128::from_le_bytes(value))
}
#[must_use]
pub fn new_os_random() -> Self {
let mut bytes = [0_u8; 16];
OsRng.fill_bytes(&mut bytes);
Self(u128::from_le_bytes(bytes))
}
}
/// Account to be used both in public and private contexts.
#[derive(
Default, Clone, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
@ -130,11 +159,12 @@ pub struct AccountWithMetadata {
#[cfg(feature = "host")]
impl AccountWithMetadata {
pub fn new(account: Account, is_authorized: bool, account_id: impl Into<AccountId>) -> Self {
#[must_use]
pub const fn new(account: Account, is_authorized: bool, account_id: AccountId) -> Self {
Self {
account,
is_authorized,
account_id: account_id.into(),
account_id,
}
}
}
@ -177,6 +207,42 @@ impl AccountId {
pub const fn into_value(self) -> [u8; 32] {
self.value
}
#[must_use]
pub fn private_account_id(npk: &NullifierPublicKey, identifier: Identifier) -> Self {
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] =
b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(PRIVATE_ACCOUNT_ID_PREFIX);
bytes.extend_from_slice(&npk.0);
bytes.extend_from_slice(&identifier.0.to_le_bytes());
Self::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Conversion should not fail"),
)
}
#[must_use]
pub fn public_account_id(value: &PublicKey) -> Self {
const PUBLIC_ACCOUNT_ID_PREFIX: &[u8; 32] =
b"/LEE/v0.3/AccountId/Public/\x00\x00\x00\x00\x00";
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(PUBLIC_ACCOUNT_ID_PREFIX);
bytes.extend_from_slice(value.value());
// bytes.extend_from_slice(&identifier.0.to_le_bytes());
Self::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Conversion should not fail"),
)
}
}
impl AsRef<[u8]> for AccountId {
@ -351,4 +417,57 @@ mod tests {
assert_eq!(nonce, nonce_restored);
}
#[test]
fn account_id_from_nullifier_public_key() {
let nsk = [
57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30,
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
];
let npk = NullifierPublicKey::from(&nsk);
let expected_account_id = AccountId::new([
48, 66, 236, 142, 62, 81, 247, 114, 151, 55, 109, 108, 34, 132, 216, 182, 239, 250,
126, 85, 106, 222, 127, 193, 125, 168, 62, 150, 129, 194, 135, 114,
]);
let identifier = Identifier(13_u128);
let account_id = AccountId::private_account_id(&npk, identifier);
assert_eq!(account_id, expected_account_id);
}
#[test]
fn account_id_from_public_key() {
let pub_key = PublicKey::try_new([42_u8; 32]).expect("Expect valid Public Key");
let expected_account_id = AccountId::new([
55, 223, 166, 27, 166, 126, 71, 128, 222, 225, 215, 176, 98, 21, 215, 13, 71, 74, 13,
72, 200, 175, 25, 19, 96, 160, 250, 230, 45, 15, 254, 134,
]);
let account_id = AccountId::public_account_id(&pub_key);
assert_eq!(account_id, expected_account_id);
}
#[test]
fn identifier_from_ephemeral_public_key() {
let epk = EphemeralPublicKey::from_scalar([
185, 147, 32, 242, 145, 91, 123, 77, 42, 33, 134, 84, 12, 165, 117, 70, 158, 201, 95,
153, 14, 12, 92, 235, 128, 156, 194, 169, 68, 35, 165, 127,
]);
let expected_identifier = Identifier(u128::from_le_bytes([
170, 216, 75, 182, 85, 117, 119, 230, 115, 121, 70, 204, 104, 96, 182, 122,
]));
let identifier = Identifier::private_identifier(&epk, 13_u8);
assert_eq!(identifier, expected_identifier);
}
#[test]
fn default_identifier() {
assert_eq!(0_u128, Identifier::default().0);
}
}

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::{
Commitment, CommitmentSetDigest, MembershipProof, Nullifier, NullifierPublicKey,
NullifierSecretKey, SharedSecretKey,
account::{Account, AccountWithMetadata},
account::{Account, AccountWithMetadata, Identifier},
encryption::Ciphertext,
program::{BlockValidityWindow, ProgramId, ProgramOutput, TimestampValidityWindow},
};
@ -22,6 +22,8 @@ pub struct PrivacyPreservingCircuitInput {
pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>,
/// Nullifier secret keys for authorized private accounts.
pub private_account_nsks: Vec<NullifierSecretKey>,
/// Identifiers used to generate `AccountId`.
pub private_account_identifiers: Vec<Identifier>,
/// Membership proofs for private accounts. Can be [`None`] for uninitialized accounts.
pub private_account_membership_proofs: Vec<Option<MembershipProof>>,
/// Program ID.
@ -57,7 +59,7 @@ mod tests {
use super::*;
use crate::{
Commitment, Nullifier, NullifierPublicKey,
account::{Account, AccountId, AccountWithMetadata, Nonce},
account::{Account, AccountId, AccountWithMetadata, Identifier, Nonce},
};
#[test]
@ -93,12 +95,21 @@ mod tests {
}],
ciphertexts: vec![Ciphertext(vec![255, 255, 1, 1, 2, 2])],
new_commitments: vec![Commitment::new(
&NullifierPublicKey::from(&[1; 32]),
&AccountId::private_account_id(
&NullifierPublicKey::from(&[1_u8; 32]),
Identifier(0_u128),
),
&Account::default(),
)],
new_nullifiers: vec![(
Nullifier::for_account_update(
&Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()),
&Commitment::new(
&AccountId::private_account_id(
&NullifierPublicKey::from(&[2_u8; 32]),
Identifier(0_u128),
),
&Account::default(),
),
&[1; 32],
),
[0xab; 32],

View File

@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use risc0_zkvm::sha::{Impl, Sha256 as _};
use serde::{Deserialize, Serialize};
use crate::{NullifierPublicKey, account::Account};
use crate::account::{Account, AccountId};
/// A commitment to all zero data.
/// ```python
@ -50,15 +50,15 @@ impl std::fmt::Debug for Commitment {
impl Commitment {
/// Generates the commitment to a private account owned by user for npk:
/// SHA256( `Comm_DS` || npk || `program_owner` || balance || nonce || SHA256(data)).
/// SHA256( `Comm_DS` || `account_id` || `program_owner` || balance || nonce || SHA256(data)).
#[must_use]
pub fn new(npk: &NullifierPublicKey, account: &Account) -> Self {
pub fn new(account_id: &AccountId, account: &Account) -> Self {
const COMMITMENT_PREFIX: &[u8; 32] =
b"/LEE/v0.3/Commitment/\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let mut bytes = Vec::new();
bytes.extend_from_slice(COMMITMENT_PREFIX);
bytes.extend_from_slice(&npk.to_byte_array());
bytes.extend_from_slice(account_id.value());
let account_bytes_with_hashed_data = {
let mut this = Vec::new();
for word in &account.program_owner {
@ -115,14 +115,15 @@ mod tests {
use risc0_zkvm::sha::{Impl, Sha256 as _};
use crate::{
Commitment, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, NullifierPublicKey, account::Account,
Commitment, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH,
account::{Account, AccountId},
};
#[test]
fn nothing_up_my_sleeve_dummy_commitment() {
let default_account = Account::default();
let npk_null = NullifierPublicKey([0; 32]);
let expected_dummy_commitment = Commitment::new(&npk_null, &default_account);
let account_id = AccountId::new([0; 32]);
let expected_dummy_commitment = Commitment::new(&account_id, &default_account);
assert_eq!(DUMMY_COMMITMENT, expected_dummy_commitment);
}

View File

@ -5,11 +5,11 @@ use chacha20::{
};
use risc0_zkvm::sha::{Impl, Sha256 as _};
use serde::{Deserialize, Serialize};
pub use shared_key_derivation::EphemeralPublicKey;
#[cfg(feature = "host")]
pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey};
pub use shared_key_derivation::{EphemeralSecretKey, ViewingPublicKey};
use crate::{Commitment, account::Account};
#[cfg(feature = "host")]
pub mod shared_key_derivation;
pub type Scalar = [u8; 32];

View File

@ -9,4 +9,13 @@ pub enum NssaCoreError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Invalid Public Key")]
InvalidPublicKey(#[source] k256::schnorr::Error),
#[error("Invalid hex for public key")]
InvalidHexPublicKey(hex::FromHexError),
#[error("Invalid private key")]
InvalidPrivateKey,
}

View File

@ -8,8 +8,11 @@ pub use commitment::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof,
compute_digest_for_path,
};
pub use encryption::{EncryptionScheme, SharedSecretKey};
pub use encryption::{
EncryptionScheme, SharedSecretKey, shared_key_derivation::EphemeralPublicKey,
};
pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey};
pub use signature::{PrivateKey, PublicKey, Signature};
pub mod account;
mod circuit_io;
@ -18,8 +21,9 @@ mod encoding;
pub mod encryption;
mod nullifier;
pub mod program;
pub mod signature;
#[cfg(feature = "host")]
// TODO: temp#[cfg(feature = "host")]
pub mod error;
pub type BlockId = u64;

View File

@ -8,23 +8,6 @@ use crate::{Commitment, account::AccountId};
#[cfg_attr(any(feature = "host", test), derive(Clone, Hash))]
pub struct NullifierPublicKey(pub [u8; 32]);
impl From<&NullifierPublicKey> for AccountId {
fn from(value: &NullifierPublicKey) -> Self {
const PRIVATE_ACCOUNT_ID_PREFIX: &[u8; 32] =
b"/LEE/v0.3/AccountId/Private/\x00\x00\x00\x00";
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX);
bytes[32..].copy_from_slice(&value.0);
Self::new(
Impl::hash_bytes(&bytes)
.as_bytes()
.try_into()
.expect("Conversion should not fail"),
)
}
}
impl AsRef<[u8]> for NullifierPublicKey {
fn as_ref(&self) -> &[u8] {
self.0.as_slice()
@ -85,10 +68,10 @@ impl Nullifier {
/// Computes a nullifier for an account initialization.
#[must_use]
pub fn for_account_initialization(npk: &NullifierPublicKey) -> Self {
pub fn for_account_initialization(account_id: &AccountId) -> Self {
const INIT_PREFIX: &[u8; 32] = b"/LEE/v0.3/Nullifier/Initialize/\x00";
let mut bytes = INIT_PREFIX.to_vec();
bytes.extend_from_slice(&npk.to_byte_array());
bytes.extend_from_slice(account_id.value());
Self(Impl::hash_bytes(&bytes).as_bytes().try_into().unwrap())
}
}
@ -96,6 +79,7 @@ impl Nullifier {
#[cfg(test)]
mod tests {
use super::*;
use crate::account::Identifier;
#[test]
fn constructor_for_account_update() {
@ -115,11 +99,16 @@ mod tests {
112, 188, 193, 129, 150, 55, 228, 67, 88, 168, 29, 151, 5, 92, 23, 190, 17, 162, 164,
255, 29, 105, 42, 186, 43, 11, 157, 168, 132, 225, 17, 163,
]);
let identifier = Identifier(0_u128);
let account_id = AccountId::private_account_id(&npk, identifier);
let expected_nullifier = Nullifier([
149, 59, 95, 181, 2, 194, 20, 143, 72, 233, 104, 243, 59, 70, 67, 243, 110, 77, 109,
132, 139, 111, 51, 125, 128, 92, 107, 46, 252, 4, 20, 149,
63, 58, 51, 159, 15, 100, 240, 243, 60, 143, 151, 108, 116, 144, 101, 6, 134, 72, 198,
249, 108, 80, 237, 194, 143, 66, 225, 191, 111, 49, 66, 54,
]);
let nullifier = Nullifier::for_account_initialization(&npk);
let nullifier = Nullifier::for_account_initialization(&account_id);
assert_eq!(nullifier, expected_nullifier);
}
@ -136,21 +125,4 @@ mod tests {
let npk = NullifierPublicKey::from(&nsk);
assert_eq!(npk, expected_npk);
}
#[test]
fn account_id_from_nullifier_public_key() {
let nsk = [
57, 5, 64, 115, 153, 56, 184, 51, 207, 238, 99, 165, 147, 214, 213, 151, 30, 251, 30,
196, 134, 22, 224, 211, 237, 120, 136, 225, 188, 220, 249, 28,
];
let npk = NullifierPublicKey::from(&nsk);
let expected_account_id = AccountId::new([
139, 72, 194, 222, 215, 187, 147, 56, 55, 35, 222, 205, 156, 12, 204, 227, 166, 44, 30,
81, 186, 14, 167, 234, 28, 236, 32, 213, 125, 251, 193, 233,
]);
let account_id = AccountId::from(&npk);
assert_eq!(account_id, expected_account_id);
}
}

View File

@ -0,0 +1,367 @@
use crate::{PrivateKey, PublicKey, Signature};
pub struct TestVector {
pub seckey: Option<PrivateKey>,
pub pubkey: PublicKey,
pub aux_rand: Option<[u8; 32]>,
pub message: Option<Vec<u8>>,
pub signature: Signature,
pub verification_result: bool,
}
/// Test vectors from
/// <https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv>.
//
pub fn test_vectors() -> Vec<TestVector> {
vec![
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0000000000000000000000000000000000000000000000000000000000000003",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(
hex::decode("0000000000000000000000000000000000000000000000000000000000000000")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000001",
)),
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906",
)),
message: Some(
hex::decode("7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
)),
message: Some(
hex::decode("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3",
),
},
verification_result: true,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4",
),
},
verification_result: true,
},
// Test with invalid public key
// TestVector {
// seckey: None,
// pubkey: PublicKey::new(hex_to_bytes(
// "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34",
// )).unwrap(),
// aux_rand: None,
// message: Some(
// hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89").unwrap(),
// ),
// signature: Signature {
// value: hex_to_bytes(
// "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
// ),
// },
// verification_result: false,
// },
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
),
},
verification_result: false,
},
TestVector {
seckey: None,
pubkey: PublicKey::try_new(hex_to_bytes(
"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
)).unwrap(),
aux_rand: None,
message: Some(
hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89")
.unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
),
},
verification_result: false,
},
// Test with invalid public key
// TestVector {
// seckey: None,
// pubkey: PublicKey::new(hex_to_bytes(
// "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
// )).unwrap(),
// aux_rand: None,
// message: Some(
// hex::decode("243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89").unwrap(),
// ),
// signature: Signature {
// value: hex_to_bytes(
// "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
// ),
// },
// verification_result: false,
// },
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: None,
signature: Signature {
value: hex_to_bytes(
"71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(hex::decode("11").unwrap()),
signature: Signature {
value: hex_to_bytes(
"08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(hex::decode("0102030405060708090A0B0C0D0E0F1011").unwrap()),
signature: Signature {
value: hex_to_bytes(
"5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5",
),
},
verification_result: true,
},
TestVector {
seckey: Some(PrivateKey::try_new(hex_to_bytes(
"0340034003400340034003400340034003400340034003400340034003400340",
)).unwrap()),
pubkey: PublicKey::try_new(hex_to_bytes(
"778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
)).unwrap(),
aux_rand: Some(hex_to_bytes::<32>(
"0000000000000000000000000000000000000000000000000000000000000000",
)),
message: Some(
hex::decode("99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999").unwrap(),
),
signature: Signature {
value: hex_to_bytes(
"403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367",
),
},
verification_result: true,
},
]
}
fn hex_to_bytes<const N: usize>(hex: &str) -> [u8; N] {
hex::decode(hex).unwrap().try_into().unwrap()
}

View File

@ -0,0 +1,127 @@
use std::str::FromStr;
use borsh::{BorshDeserialize, BorshSerialize};
pub use private_key::PrivateKey;
pub use public_key::PublicKey;
use rand::{RngCore as _, rngs::OsRng};
mod private_key;
mod public_key;
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Signature {
pub value: [u8; 64],
}
impl std::fmt::Debug for Signature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for Signature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.value))
}
}
impl FromStr for Signature {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0_u8; 64];
hex::decode_to_slice(s, &mut bytes)?;
Ok(Self { value: bytes })
}
}
impl Signature {
#[must_use]
pub fn new(key: &PrivateKey, message: &[u8]) -> Self {
let mut aux_random = [0_u8; 32];
OsRng.fill_bytes(&mut aux_random);
Self::new_with_aux_random(key, message, aux_random)
}
pub(crate) fn new_with_aux_random(
key: &PrivateKey,
message: &[u8],
aux_random: [u8; 32],
) -> Self {
let value = {
let signing_key = k256::schnorr::SigningKey::from_bytes(key.value())
.expect("Expect valid signing key");
signing_key
.sign_raw(message, &aux_random)
.expect("Expect to produce a valid signature")
.to_bytes()
};
Self { value }
}
#[must_use]
pub fn is_valid_for(&self, bytes: &[u8], public_key: &PublicKey) -> bool {
let Ok(pk) = k256::schnorr::VerifyingKey::from_bytes(public_key.value()) else {
return false;
};
let Ok(sig) = k256::schnorr::Signature::try_from(self.value.as_slice()) else {
return false;
};
pk.verify_raw(bytes, &sig).is_ok()
}
#[cfg(feature = "test-utils")]
#[must_use]
pub const fn new_for_tests(value: [u8; 64]) -> Self {
Self { value }
}
}
#[cfg(test)]
mod bip340_test_vectors;
#[cfg(test)]
mod tests {
use crate::{Signature, signature::bip340_test_vectors};
#[test]
fn signature_generation_from_bip340_test_vectors() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let Some(private_key) = test_vector.seckey else {
continue;
};
let Some(aux_random) = test_vector.aux_rand else {
continue;
};
let Some(message) = test_vector.message else {
continue;
};
if !test_vector.verification_result {
continue;
}
let expected_signature = &test_vector.signature;
let signature = Signature::new_with_aux_random(&private_key, &message, aux_random);
assert_eq!(&signature, expected_signature, "Failed test vector {i}");
}
}
#[test]
fn signature_verification_from_bip340_test_vectors() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let message = test_vector.message.unwrap_or(vec![]);
let expected_result = test_vector.verification_result;
let result = test_vector
.signature
.is_valid_for(&message, &test_vector.pubkey);
assert_eq!(result, expected_result, "Failed test vector {i}");
}
}
}

View File

@ -0,0 +1,78 @@
use std::str::FromStr;
use rand::{Rng as _, rngs::OsRng};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use crate::error::NssaCoreError;
// TODO: Remove Debug, Clone, Serialize, Deserialize, PartialEq and Eq for security reasons
// TODO: Implement Zeroize
#[derive(Clone, SerializeDisplay, DeserializeFromStr, PartialEq, Eq)]
pub struct PrivateKey([u8; 32]);
impl std::fmt::Debug for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for PrivateKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
impl FromStr for PrivateKey {
type Err = NssaCoreError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0_u8; 32];
hex::decode_to_slice(s, &mut bytes).map_err(|_err| NssaCoreError::InvalidPrivateKey)?;
Self::try_new(bytes)
}
}
impl PrivateKey {
#[must_use]
pub fn new_os_random() -> Self {
let mut rng = OsRng;
loop {
if let Ok(key) = Self::try_new(rng.r#gen()) {
break key;
}
}
}
fn is_valid_key(value: [u8; 32]) -> bool {
k256::SecretKey::from_bytes(&value.into()).is_ok()
}
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaCoreError> {
if Self::is_valid_key(value) {
Ok(Self(value))
} else {
Err(NssaCoreError::InvalidPrivateKey)
}
}
#[must_use]
pub const fn value(&self) -> &[u8; 32] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn value_getter() {
let key = PrivateKey::try_new([1; 32]).unwrap();
assert_eq!(key.value(), &key.0);
}
#[test]
fn produce_key() {
let _key = PrivateKey::new_os_random();
}
}

View File

@ -0,0 +1,135 @@
use std::str::FromStr;
use borsh::{BorshDeserialize, BorshSerialize};
use k256::elliptic_curve::sec1::ToEncodedPoint as _;
use serde_with::{DeserializeFromStr, SerializeDisplay};
use crate::{PrivateKey, error::NssaCoreError};
#[derive(Clone, PartialEq, Eq, BorshSerialize, SerializeDisplay, DeserializeFromStr)]
pub struct PublicKey([u8; 32]);
impl std::fmt::Debug for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
impl FromStr for PublicKey {
type Err = NssaCoreError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0_u8; 32];
hex::decode_to_slice(s, &mut bytes).map_err(NssaCoreError::InvalidHexPublicKey)?;
Self::try_new(bytes)
}
}
impl BorshDeserialize for PublicKey {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
let mut buf = [0_u8; 32];
reader.read_exact(&mut buf)?;
Self::try_new(buf).map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
}
}
impl PublicKey {
#[must_use]
pub fn new_from_private_key(key: &PrivateKey) -> Self {
let value = {
let secret_key = k256::SecretKey::from_bytes(&(*key.value()).into())
.expect("Expect a valid private key");
let encoded = secret_key.public_key().to_encoded_point(false);
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)
}
pub fn try_new(value: [u8; 32]) -> Result<Self, NssaCoreError> {
// Check point is a valid x-only public key
let _ = k256::schnorr::VerifyingKey::from_bytes(&value)
.map_err(NssaCoreError::InvalidPublicKey)?;
Ok(Self(value))
}
#[must_use]
pub const fn value(&self) -> &[u8; 32] {
&self.0
}
}
#[cfg(test)]
mod test {
use crate::{PublicKey, error::NssaCoreError, signature::bip340_test_vectors};
#[test]
fn try_new_invalid_public_key_from_bip340_test_vectors_5() {
let value_invalid_key = [
238, 253, 234, 76, 219, 103, 119, 80, 164, 32, 254, 232, 7, 234, 207, 33, 235, 152,
152, 174, 121, 185, 118, 135, 102, 228, 250, 160, 74, 45, 74, 52,
];
let result = PublicKey::try_new(value_invalid_key);
assert!(matches!(result, Err(NssaCoreError::InvalidPublicKey(_))));
}
#[test]
fn try_new_invalid_public_key_from_bip340_test_vector_14() {
let value_invalid_key = [
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 252, 48,
];
let result = PublicKey::try_new(value_invalid_key);
assert!(matches!(result, Err(NssaCoreError::InvalidPublicKey(_))));
}
#[test]
fn try_new_valid_public_keys() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let expected_public_key = test_vector.pubkey;
let public_key = PublicKey::try_new(*expected_public_key.value()).unwrap();
assert_eq!(public_key, expected_public_key, "Failed on test vector {i}");
}
}
#[test]
fn public_key_generation_from_bip340_test_vectors() {
for (i, test_vector) in bip340_test_vectors::test_vectors().into_iter().enumerate() {
let Some(private_key) = &test_vector.seckey else {
continue;
};
let public_key = PublicKey::new_from_private_key(private_key);
let expected_public_key = &test_vector.pubkey;
assert_eq!(
&public_key, expected_public_key,
"Failed test vector at index {i}"
);
}
}
#[test]
fn correct_ser_deser_roundtrip() {
let pub_key = PublicKey::try_new([42; 32]).unwrap();
let pub_key_borsh_ser = borsh::to_vec(&pub_key).unwrap();
let pub_key_new: PublicKey = borsh::from_slice(&pub_key_borsh_ser).unwrap();
assert_eq!(pub_key, pub_key_new);
}
}

View File

@ -22,18 +22,9 @@ pub enum NssaError {
#[error("Serialization error: {0}")]
InstructionSerializationError(String),
#[error("Invalid private key")]
InvalidPrivateKey,
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Invalid Public Key")]
InvalidPublicKey(#[source] k256::schnorr::Error),
#[error("Invalid hex for public key")]
InvalidHexPublicKey(hex::FromHexError),
#[error("Risc0 error: {0}")]
ProgramWriteInputFailed(String),

View File

@ -15,7 +15,6 @@ pub use privacy_preserving_transaction::{
pub use program_deployment_transaction::ProgramDeploymentTransaction;
pub use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID;
pub use public_transaction::PublicTransaction;
pub use signature::{PrivateKey, PublicKey, Signature};
pub use state::{
CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID,
CLOCK_PROGRAM_ACCOUNT_IDS, V03State,
@ -29,7 +28,6 @@ pub mod privacy_preserving_transaction;
pub mod program;
pub mod program_deployment_transaction;
pub mod public_transaction;
mod signature;
mod state;
mod validated_state_diff;

View File

@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
PrivacyPreservingCircuitOutput, SharedSecretKey,
account::AccountWithMetadata,
account::{AccountWithMetadata, Identifier},
program::{ChainedCall, InstructionData, ProgramId, ProgramOutput},
};
use risc0_zkvm::{ExecutorEnv, InnerReceipt, ProverOpts, Receipt, default_prover};
@ -63,13 +63,14 @@ impl From<Program> for ProgramWithDependencies {
/// Generates a proof of the execution of a NSSA program inside the privacy preserving execution
/// circuit.
/// TODO: too many parameters.
#[expect(clippy::too_many_arguments, reason = "TODO: fix this later")]
pub fn execute_and_prove(
pre_states: Vec<AccountWithMetadata>,
instruction_data: InstructionData,
visibility_mask: Vec<u8>,
private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>,
private_account_nsks: Vec<NullifierSecretKey>,
private_account_identifiers: Vec<Identifier>,
private_account_membership_proofs: Vec<Option<MembershipProof>>,
program_with_dependencies: &ProgramWithDependencies,
) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> {
@ -129,6 +130,7 @@ pub fn execute_and_prove(
visibility_mask,
private_account_keys,
private_account_nsks,
private_account_identifiers,
private_account_membership_proofs,
program_id: program_with_dependencies.program.id(),
};
@ -183,7 +185,7 @@ mod tests {
use nssa_core::{
Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
account::{Account, AccountId, AccountWithMetadata, Identifier, Nonce, data::Data},
};
use super::*;
@ -214,7 +216,7 @@ mod tests {
let recipient = AccountWithMetadata::new(
Account::default(),
false,
AccountId::from(&recipient_keys.npk()),
AccountId::private_account_id(&recipient_keys.npk(), recipient_keys.identifier),
);
let balance_to_move: u128 = 37;
@ -244,6 +246,7 @@ mod tests {
vec![0, 2],
vec![(recipient_keys.npk(), shared_secret)],
vec![],
vec![recipient_keys.identifier],
vec![None],
&Program::authenticated_transfer_program().into(),
)
@ -274,6 +277,8 @@ mod tests {
let program = Program::authenticated_transfer_program();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let recipient_id =
AccountId::private_account_id(&recipient_keys.npk(), recipient_keys.identifier);
let sender_nonce = Nonce(0xdead_beef);
let sender_pre = AccountWithMetadata::new(
@ -284,14 +289,14 @@ mod tests {
data: Data::default(),
},
true,
AccountId::from(&sender_keys.npk()),
AccountId::private_account_id(&sender_keys.npk(), sender_keys.identifier),
);
let commitment_sender = Commitment::new(&sender_keys.npk(), &sender_pre.account);
let commitment_sender = Commitment::new(&sender_pre.account_id, &sender_pre.account);
let recipient = AccountWithMetadata::new(
Account::default(),
false,
AccountId::from(&recipient_keys.npk()),
AccountId::private_account_id(&recipient_keys.npk(), recipient_keys.identifier),
);
let balance_to_move: u128 = 37;
@ -304,7 +309,7 @@ mod tests {
commitment_set.digest(),
),
(
Nullifier::for_account_initialization(&recipient_keys.npk()),
Nullifier::for_account_initialization(&recipient_id),
DUMMY_COMMITMENT_HASH,
),
];
@ -324,8 +329,8 @@ mod tests {
..Default::default()
};
let expected_new_commitments = vec![
Commitment::new(&sender_keys.npk(), &expected_private_account_1),
Commitment::new(&recipient_keys.npk(), &expected_private_account_2),
Commitment::new(&sender_pre.account_id, &expected_private_account_1),
Commitment::new(&recipient.account_id, &expected_private_account_2),
];
let esk_1 = [3; 32];
@ -343,6 +348,7 @@ mod tests {
(recipient_keys.npk(), shared_secret_2),
],
vec![sender_keys.nsk],
vec![sender_keys.identifier, recipient_keys.identifier],
vec![commitment_set.get_proof_for(&commitment_sender), None],
&program.into(),
)
@ -380,7 +386,7 @@ mod tests {
let pre = AccountWithMetadata::new(
Account::default(),
false,
AccountId::from(&account_keys.npk()),
AccountId::private_account_id(&account_keys.npk(), Identifier(42_u128)),
);
let validity_window_chain_caller = Program::validity_window_chain_caller();
@ -409,6 +415,7 @@ mod tests {
vec![2],
vec![(account_keys.npk(), shared_secret)],
vec![],
vec![Identifier(42_u128)],
vec![None],
&program_with_deps,
);

View File

@ -1,6 +1,6 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{
Commitment, CommitmentSetDigest, Nullifier, NullifierPublicKey, PrivacyPreservingCircuitOutput,
Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput,
account::{Account, Nonce},
encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey},
program::{BlockValidityWindow, TimestampValidityWindow},
@ -21,11 +21,11 @@ pub struct EncryptedAccountData {
impl EncryptedAccountData {
fn new(
ciphertext: Ciphertext,
npk: &NullifierPublicKey,
account_id: &AccountId,
vpk: &ViewingPublicKey,
epk: EphemeralPublicKey,
) -> Self {
let view_tag = Self::compute_view_tag(npk, vpk);
let view_tag = Self::compute_view_tag(account_id, vpk);
Self {
ciphertext,
epk,
@ -35,10 +35,13 @@ impl EncryptedAccountData {
/// Computes the tag as the first byte of SHA256("/LEE/v0.3/ViewTag/" || Npk || vpk).
#[must_use]
pub fn compute_view_tag(npk: &NullifierPublicKey, vpk: &ViewingPublicKey) -> ViewTag {
pub fn compute_view_tag(account_id: &AccountId, vpk: &ViewingPublicKey) -> ViewTag {
const VIEWTAG_PREFIX: &[u8; 32] =
b"/LEE/v0.3/ViewTag\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
let mut hasher = Sha256::new();
hasher.update(b"/LEE/v0.3/ViewTag/");
hasher.update(npk.to_byte_array());
hasher.update(VIEWTAG_PREFIX);
hasher.update(account_id.to_bytes());
hasher.update(vpk.to_bytes());
let digest: [u8; 32] = hasher.finalize().into();
digest[0]
@ -90,7 +93,10 @@ impl Message {
pub fn try_from_circuit_output(
public_account_ids: Vec<AccountId>,
nonces: Vec<Nonce>,
public_keys: Vec<(NullifierPublicKey, ViewingPublicKey, EphemeralPublicKey)>,
public_keys: Vec<(AccountId, ViewingPublicKey, EphemeralPublicKey)>, /* TODO: Rename
* `public_keys` to
* account for
* `account_id`. */
output: PrivacyPreservingCircuitOutput,
) -> Result<Self, NssaError> {
if public_keys.len() != output.ciphertexts.len() {
@ -103,8 +109,8 @@ impl Message {
.ciphertexts
.into_iter()
.zip(public_keys)
.map(|(ciphertext, (npk, vpk, epk))| {
EncryptedAccountData::new(ciphertext, &npk, &vpk, epk)
.map(|(ciphertext, (account_id, vpk, epk))| {
EncryptedAccountData::new(ciphertext, &account_id, &vpk, epk)
})
.collect();
Ok(Self {
@ -124,7 +130,7 @@ impl Message {
pub mod tests {
use nssa_core::{
Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, SharedSecretKey,
account::Account,
account::{Account, Identifier},
encryption::{EphemeralPublicKey, ViewingPublicKey},
program::{BlockValidityWindow, TimestampValidityWindow},
};
@ -146,7 +152,9 @@ pub mod tests {
let npk1 = NullifierPublicKey::from(&nsk1);
let npk2 = NullifierPublicKey::from(&nsk2);
let public_account_ids = vec![AccountId::new([1; 32])];
let account_id1 = AccountId::private_account_id(&npk1, Identifier(0_u128));
let account_id2 = AccountId::private_account_id(&npk2, Identifier(0_u128));
let public_account_ids = vec![account_id1, account_id2];
let nonces = vec![1_u128.into(), 2_u128.into(), 3_u128.into()];
@ -154,9 +162,9 @@ pub mod tests {
let encrypted_private_post_states = Vec::new();
let new_commitments = vec![Commitment::new(&npk2, &account2)];
let new_commitments = vec![Commitment::new(&account_id2, &account2)];
let old_commitment = Commitment::new(&npk1, &account1);
let old_commitment = Commitment::new(&account_id1, &account1);
let new_nullifiers = vec![(
Nullifier::for_account_update(&old_commitment, &nsk1),
[0; 32],
@ -178,19 +186,22 @@ pub mod tests {
fn encrypted_account_data_constructor() {
let npk = NullifierPublicKey::from(&[1; 32]);
let vpk = ViewingPublicKey::from_scalar([2; 32]);
let account_id = AccountId::private_account_id(&npk, Identifier(0_u128));
let account = Account::default();
let commitment = Commitment::new(&npk, &account);
let commitment = Commitment::new(&account_id, &account);
let esk = [3; 32];
let shared_secret = SharedSecretKey::new(&esk, &vpk);
let epk = EphemeralPublicKey::from_scalar(esk);
let ciphertext = EncryptionScheme::encrypt(&account, &shared_secret, &commitment, 2);
let encrypted_account_data =
EncryptedAccountData::new(ciphertext.clone(), &npk, &vpk, epk.clone());
EncryptedAccountData::new(ciphertext.clone(), &account_id, &vpk, epk.clone());
let expected_view_tag = {
let mut hasher = Sha256::new();
hasher.update(b"/LEE/v0.3/ViewTag/");
hasher.update(npk.to_byte_array());
hasher.update(
b"/LEE/v0.3/ViewTag\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
);
hasher.update(account_id.to_bytes());
hasher.update(vpk.to_bytes());
let digest: [u8; 32] = hasher.finalize().into();
digest[0]
@ -200,7 +211,7 @@ pub mod tests {
assert_eq!(encrypted_account_data.epk, epk);
assert_eq!(
encrypted_account_data.view_tag,
EncryptedAccountData::compute_view_tag(&npk, &vpk)
EncryptedAccountData::compute_view_tag(&account_id, &vpk)
);
assert_eq!(encrypted_account_data.view_tag, expected_view_tag);
}

View File

@ -43,7 +43,7 @@ impl PrivacyPreservingTransaction {
self.witness_set
.signatures_and_public_keys()
.iter()
.map(|(_, public_key)| AccountId::from(public_key))
.map(|(_, public_key)| AccountId::public_account_id(public_key))
.collect()
}
@ -61,8 +61,10 @@ impl PrivacyPreservingTransaction {
#[cfg(test)]
mod tests {
use nssa_core::{PrivateKey, PublicKey};
use crate::{
AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey,
AccountId, PrivacyPreservingTransaction,
privacy_preserving_transaction::{
circuit::Proof, message::tests::message_for_tests, witness_set::WitnessSet,
},
@ -71,8 +73,8 @@ mod tests {
fn keys_for_tests() -> (PrivateKey, PrivateKey, AccountId, AccountId) {
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let addr1 = AccountId::from(&PublicKey::new_from_private_key(&key1));
let addr2 = AccountId::from(&PublicKey::new_from_private_key(&key2));
let addr1 = AccountId::public_account_id(&PublicKey::new_from_private_key(&key1));
let addr2 = AccountId::public_account_id(&PublicKey::new_from_private_key(&key2));
(key1, key2, addr1, addr2)
}

View File

@ -1,9 +1,7 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{PrivateKey, PublicKey, Signature};
use crate::{
PrivateKey, PublicKey, Signature,
privacy_preserving_transaction::{circuit::Proof, message::Message},
};
use crate::privacy_preserving_transaction::{circuit::Proof, message::Message};
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct WitnessSet {

View File

@ -35,7 +35,7 @@ impl PublicTransaction {
self.witness_set
.signatures_and_public_keys()
.iter()
.map(|(_, public_key)| AccountId::from(public_key))
.map(|(_, public_key)| AccountId::public_account_id(public_key))
.collect()
}
@ -61,10 +61,11 @@ impl PublicTransaction {
#[cfg(test)]
pub mod tests {
use nssa_core::{PrivateKey, PublicKey};
use sha2::{Digest as _, digest::FixedOutput as _};
use crate::{
AccountId, PrivateKey, PublicKey, PublicTransaction, Signature, V03State,
AccountId, PublicTransaction, V03State,
error::NssaError,
program::Program,
public_transaction::{Message, WitnessSet},
@ -74,8 +75,8 @@ pub mod tests {
fn keys_for_tests() -> (PrivateKey, PrivateKey, AccountId, AccountId) {
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let addr1 = AccountId::from(&PublicKey::new_from_private_key(&key1));
let addr2 = AccountId::from(&PublicKey::new_from_private_key(&key2));
let addr1 = AccountId::public_account_id(&PublicKey::new_from_private_key(&key1));
let addr2 = AccountId::public_account_id(&PublicKey::new_from_private_key(&key2));
(key1, key2, addr1, addr2)
}
@ -201,8 +202,11 @@ pub mod tests {
assert!(matches!(result, Err(NssaError::InvalidInput(_))));
}
#[cfg(feature = "test-utils")]
#[test]
fn all_signatures_must_be_valid() {
use nssa_core::Signature;
let (key1, key2, addr1, addr2) = keys_for_tests();
let state = state_for_tests();
let nonces = vec![0_u128.into(), 0_u128.into()];

View File

@ -1,6 +1,7 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::{PrivateKey, PublicKey, Signature};
use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message};
use crate::public_transaction::Message;
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct WitnessSet {
@ -65,8 +66,8 @@ mod tests {
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let pubkey1 = PublicKey::new_from_private_key(&key1);
let pubkey2 = PublicKey::new_from_private_key(&key2);
let addr1 = AccountId::from(&pubkey1);
let addr2 = AccountId::from(&pubkey2);
let addr1 = AccountId::public_account_id(&pubkey1);
let addr2 = AccountId::public_account_id(&pubkey2);
let nonces = vec![1_u128.into(), 2_u128.into()];
let instruction = vec![1, 2, 3, 4];
let message = Message::try_new([0; 8], vec![addr1, addr2], nonces, instruction).unwrap();

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ use nssa_core::{
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, MembershipProof,
Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
PrivacyPreservingCircuitOutput, SharedSecretKey,
account::{Account, AccountId, AccountWithMetadata, Nonce},
account::{Account, AccountId, AccountWithMetadata, Identifier, Nonce},
compute_digest_for_path,
program::{
AccountPostState, BlockValidityWindow, ChainedCall, Claim, DEFAULT_PROGRAM_ID,
@ -304,6 +304,7 @@ fn compute_circuit_output(
visibility_mask: &[u8],
private_account_keys: &[(NullifierPublicKey, SharedSecretKey)],
private_account_nsks: &[NullifierSecretKey],
private_account_identifiers: &[Identifier],
private_account_membership_proofs: &[Option<MembershipProof>],
) -> PrivacyPreservingCircuitOutput {
let mut output = PrivacyPreservingCircuitOutput {
@ -326,6 +327,7 @@ fn compute_circuit_output(
let mut private_keys_iter = private_account_keys.iter();
let mut private_nsks_iter = private_account_nsks.iter();
let mut private_membership_proofs_iter = private_account_membership_proofs.iter();
let mut private_identifiers_iter = private_account_identifiers.iter();
let mut output_index = 0;
for (account_visibility_mask, (pre_state, post_state)) in
@ -342,8 +344,12 @@ fn compute_circuit_output(
panic!("Missing private account key");
};
let Some(identifier) = private_identifiers_iter.next() else {
panic!("Missing private account identifier");
};
assert_eq!(
AccountId::from(npk),
AccountId::private_account_id(npk, *identifier),
pre_state.account_id,
"AccountId mismatch"
);
@ -371,11 +377,11 @@ fn compute_circuit_output(
let Some(membership_proof_opt) = private_membership_proofs_iter.next() else {
panic!("Missing membership proof");
};
// TODO: here is the issue
let new_nullifier = compute_nullifier_and_set_digest(
membership_proof_opt.as_ref(),
&pre_state.account,
npk,
&pre_state.account_id,
nsk,
);
@ -405,7 +411,8 @@ fn compute_circuit_output(
"Membership proof must be None for unauthorized accounts"
);
let nullifier = Nullifier::for_account_initialization(npk);
let account_id = AccountId::private_account_id(npk, *identifier);
let nullifier = Nullifier::for_account_initialization(&account_id);
let new_nonce = Nonce::private_account_nonce_init(npk);
@ -418,7 +425,8 @@ fn compute_circuit_output(
post_with_updated_nonce.nonce = new_nonce;
// Compute commitment
let commitment_post = Commitment::new(npk, &post_with_updated_nonce);
let commitment_post =
Commitment::new(&pre_state.account_id, &post_with_updated_nonce);
// Encrypt and push post state
let encrypted_account = EncryptionScheme::encrypt(
@ -456,10 +464,11 @@ fn compute_circuit_output(
output
}
// Marvin: todo
fn compute_nullifier_and_set_digest(
membership_proof_opt: Option<&MembershipProof>,
pre_account: &Account,
npk: &NullifierPublicKey,
account_id: &AccountId,
nsk: &NullifierSecretKey,
) -> (Nullifier, CommitmentSetDigest) {
membership_proof_opt.as_ref().map_or_else(
@ -467,16 +476,16 @@ fn compute_nullifier_and_set_digest(
assert_eq!(
*pre_account,
Account::default(),
"Found new private account with non default values"
"Found new private account with non default values$Marvin$"
);
// Compute initialization nullifier
let nullifier = Nullifier::for_account_initialization(npk);
let nullifier = Nullifier::for_account_initialization(account_id);
(nullifier, DUMMY_COMMITMENT_HASH)
},
|membership_proof| {
// Compute commitment set digest associated with provided auth path
let commitment_pre = Commitment::new(npk, pre_account);
let commitment_pre = Commitment::new(account_id, pre_account);
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
// Compute update nullifier
@ -492,6 +501,7 @@ fn main() {
visibility_mask,
private_account_keys,
private_account_nsks,
private_account_identifiers,
private_account_membership_proofs,
program_id,
} = env::read();
@ -504,6 +514,7 @@ fn main() {
&visibility_mask,
&private_account_keys,
&private_account_nsks,
&private_account_identifiers,
&private_account_membership_proofs,
);

View File

@ -4,10 +4,9 @@ use amm_core::{
PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
compute_pool_pda, compute_vault_pda, compute_vault_pda_seed,
};
use nssa::{
PrivateKey, PublicKey, PublicTransaction, V03State, program::Program, public_transaction,
};
use nssa::{PublicTransaction, V03State, program::Program, public_transaction};
use nssa_core::{
PrivateKey, PublicKey,
account::{Account, AccountId, AccountWithMetadata, Data},
program::{ChainedCall, ProgramId},
};
@ -1314,19 +1313,19 @@ impl IdForExeTests {
}
fn user_token_a_id() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(
AccountId::public_account_id(&PublicKey::new_from_private_key(
&PrivateKeysForTests::user_token_a_key(),
))
}
fn user_token_b_id() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(
AccountId::public_account_id(&PublicKey::new_from_private_key(
&PrivateKeysForTests::user_token_b_key(),
))
}
fn user_token_lp_id() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(
AccountId::public_account_id(&PublicKey::new_from_private_key(
&PrivateKeysForTests::user_token_lp_key(),
))
}

View File

@ -14,7 +14,7 @@ pub struct SequencerStore {
// TODO: Consider adding the hashmap to the database for faster recovery.
tx_hash_to_block_map: HashMap<HashType, u64>,
genesis_id: u64,
signing_key: nssa::PrivateKey,
signing_key: nssa_core::PrivateKey,
}
impl SequencerStore {
@ -26,7 +26,7 @@ impl SequencerStore {
location: &Path,
genesis_block: &Block,
genesis_msg_id: MantleMsgId,
signing_key: nssa::PrivateKey,
signing_key: nssa_core::PrivateKey,
) -> Result<Self> {
let tx_hash_to_block_map = block_to_transactions_map(genesis_block);
@ -80,7 +80,7 @@ impl SequencerStore {
self.genesis_id
}
pub const fn signing_key(&self) -> &nssa::PrivateKey {
pub const fn signing_key(&self) -> &nssa_core::PrivateKey {
&self.signing_key
}

View File

@ -15,7 +15,8 @@ use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SI
use mempool::{MemPool, MemPoolHandle};
#[cfg(feature = "mock")]
pub use mock::SequencerCoreWithMockClients;
use nssa::V03State;
use nssa::{AccountId, V03State};
use nssa_core::account::Identifier;
pub use storage::error::DbError;
use testnet_initial_state::initial_state;
@ -62,7 +63,7 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
timestamp: 0,
};
let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap();
let signing_key = nssa_core::PrivateKey::try_new(config.signing_key).unwrap();
let genesis_parent_msg_id = [0; 32];
let genesis_block = hashable_data.into_pending_block(&signing_key, genesis_parent_msg_id);
@ -113,11 +114,11 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
let npk = &init_comm_data.npk;
let mut acc = init_comm_data.account.clone();
let acc_id = &AccountId::private_account_id(npk, Identifier(0_u128)); //TODO marvin
acc.program_owner =
nssa::program::Program::authenticated_transfer_program().id();
nssa_core::Commitment::new(npk, &acc)
nssa_core::Commitment::new(acc_id, &acc)
})
.collect()
});
@ -432,11 +433,11 @@ mod tests {
}
}
fn create_signing_key_for_account1() -> nssa::PrivateKey {
fn create_signing_key_for_account1() -> nssa_core::PrivateKey {
initial_pub_accounts_private_keys()[0].pub_sign_key.clone()
}
fn create_signing_key_for_account2() -> nssa::PrivateKey {
fn create_signing_key_for_account2() -> nssa_core::PrivateKey {
initial_pub_accounts_private_keys()[1].pub_sign_key.clone()
}

View File

@ -10,6 +10,7 @@ workspace = true
[dependencies]
common.workspace = true
nssa.workspace = true
nssa_core.workspace = true
thiserror.workspace = true
borsh.workspace = true

View File

@ -213,7 +213,8 @@ fn closest_breakpoint_id(block_id: u64) -> u64 {
#[expect(clippy::shadow_unrelated, reason = "Fine for tests")]
#[cfg(test)]
mod tests {
use nssa::{AccountId, PublicKey};
use nssa::AccountId;
use nssa_core::PublicKey;
use tempfile::tempdir;
use super::*;
@ -222,20 +223,20 @@ mod tests {
common::test_utils::produce_dummy_block(1, None, vec![])
}
fn acc1_sign_key() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([1; 32]).unwrap()
fn acc1_sign_key() -> nssa_core::PrivateKey {
nssa_core::PrivateKey::try_new([1; 32]).unwrap()
}
fn acc2_sign_key() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([2; 32]).unwrap()
fn acc2_sign_key() -> nssa_core::PrivateKey {
nssa_core::PrivateKey::try_new([2; 32]).unwrap()
}
fn acc1() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&acc1_sign_key()))
AccountId::public_account_id(&PublicKey::new_from_private_key(&acc1_sign_key()))
}
fn acc2() -> AccountId {
AccountId::from(&PublicKey::new_from_private_key(&acc2_sign_key()))
AccountId::public_account_id(&PublicKey::new_from_private_key(&acc2_sign_key()))
}
#[test]

View File

@ -3,8 +3,11 @@ use key_protocol::key_management::{
KeyChain,
secret_holders::{PrivateKeyHolder, SecretSpendingKey},
};
use nssa::{Account, AccountId, Data, PrivateKey, PublicKey, V03State};
use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point};
use nssa::{Account, AccountId, Data, V03State};
use nssa_core::{
NullifierPublicKey, PrivateKey, PublicKey, account::Identifier,
encryption::shared_key_derivation::Secp256k1Point,
};
use serde::{Deserialize, Serialize};
const PRIVATE_KEY_PUB_ACC_A: [u8; 32] = [
@ -75,6 +78,9 @@ const PUB_ACC_B_INITIAL_BALANCE: u128 = 20000;
const PRIV_ACC_A_INITIAL_BALANCE: u128 = 10000;
const PRIV_ACC_B_INITIAL_BALANCE: u128 = 20000;
const PRIV_ACC_A_IDENTIFIER: u128 = 13;
const PRIV_ACC_B_IDENTIFIER: u128 = 42;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PublicAccountPublicInitialData {
pub account_id: AccountId,
@ -84,19 +90,21 @@ pub struct PublicAccountPublicInitialData {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PrivateAccountPublicInitialData {
pub npk: nssa_core::NullifierPublicKey,
pub identifier: Identifier,
pub account: nssa_core::account::Account,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PublicAccountPrivateInitialData {
pub account_id: nssa::AccountId,
pub pub_sign_key: nssa::PrivateKey,
pub pub_sign_key: nssa_core::PrivateKey,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrivateAccountPrivateInitialData {
pub account_id: nssa::AccountId,
pub account: nssa_core::account::Account,
pub identifier: Identifier,
pub key_chain: KeyChain,
}
@ -108,11 +116,15 @@ pub fn initial_pub_accounts_private_keys() -> Vec<PublicAccountPrivateInitialDat
vec![
PublicAccountPrivateInitialData {
account_id: AccountId::from(&PublicKey::new_from_private_key(&acc1_pub_sign_key)),
account_id: AccountId::public_account_id(&PublicKey::new_from_private_key(
&acc1_pub_sign_key,
)),
pub_sign_key: acc1_pub_sign_key,
},
PublicAccountPrivateInitialData {
account_id: AccountId::from(&PublicKey::new_from_private_key(&acc2_pub_sign_key)),
account_id: AccountId::public_account_id(&PublicKey::new_from_private_key(
&acc2_pub_sign_key,
)),
pub_sign_key: acc2_pub_sign_key,
},
]
@ -140,25 +152,36 @@ pub fn initial_priv_accounts_private_keys() -> Vec<PrivateAccountPrivateInitialD
viewing_public_key: Secp256k1Point(VPK_PRIV_ACC_B.to_vec()),
};
let identifier_1 = Identifier(PRIV_ACC_A_IDENTIFIER);
let identifier_2 = Identifier(PRIV_ACC_B_IDENTIFIER);
vec![
PrivateAccountPrivateInitialData {
account_id: AccountId::from(&key_chain_1.nullifier_public_key),
account_id: AccountId::private_account_id(
&key_chain_1.nullifier_public_key,
Identifier(13_u128),
),
account: Account {
program_owner: DEFAULT_PROGRAM_OWNER,
balance: PRIV_ACC_A_INITIAL_BALANCE,
data: Data::default(),
nonce: 0.into(),
},
identifier: identifier_1,
key_chain: key_chain_1,
},
PrivateAccountPrivateInitialData {
account_id: AccountId::from(&key_chain_2.nullifier_public_key),
account_id: AccountId::private_account_id(
&key_chain_2.nullifier_public_key,
Identifier(42_u128),
),
account: Account {
program_owner: DEFAULT_PROGRAM_OWNER,
balance: PRIV_ACC_B_INITIAL_BALANCE,
data: Data::default(),
nonce: 0.into(),
},
identifier: identifier_2,
key_chain: key_chain_2,
},
]
@ -170,6 +193,7 @@ pub fn initial_commitments() -> Vec<PrivateAccountPublicInitialData> {
.into_iter()
.map(|data| PrivateAccountPublicInitialData {
npk: data.key_chain.nullifier_public_key.clone(),
identifier: data.identifier,
account: data.account,
})
.collect()
@ -199,13 +223,14 @@ pub fn initial_state() -> V03State {
let initial_commitments: Vec<nssa_core::Commitment> = initial_commitments()
.iter()
.map(|init_comm_data| {
let npk = &init_comm_data.npk;
let acc_id =
&AccountId::private_account_id(&init_comm_data.npk, init_comm_data.identifier);
let mut acc = init_comm_data.account.clone();
acc.program_owner = nssa::program::Program::authenticated_transfer_program().id();
nssa_core::Commitment::new(npk, &acc)
nssa_core::Commitment::new(acc_id, &acc)
})
.collect();
@ -371,6 +396,7 @@ mod tests {
init_comms[0],
PrivateAccountPublicInitialData {
npk: NullifierPublicKey(NPK_PRIV_ACC_A),
identifier: Identifier(PRIV_ACC_A_IDENTIFIER),
account: Account {
program_owner: DEFAULT_PROGRAM_OWNER,
balance: PRIV_ACC_A_INITIAL_BALANCE,
@ -384,6 +410,7 @@ mod tests {
init_comms[1],
PrivateAccountPublicInitialData {
npk: NullifierPublicKey(NPK_PRIV_ACC_B),
identifier: Identifier(PRIV_ACC_B_IDENTIFIER),
account: Account {
program_owner: DEFAULT_PROGRAM_OWNER,
balance: PRIV_ACC_B_INITIAL_BALANCE,

View File

@ -2,7 +2,8 @@
use std::ptr;
use nssa::{AccountId, PublicKey};
use nssa::AccountId;
use nssa_core::PublicKey;
use crate::{
error::{print_error, WalletFfiError},

View File

@ -249,15 +249,15 @@ impl TryFrom<&FfiAccount> for nssa::Account {
}
}
impl From<nssa::PublicKey> for FfiPublicAccountKey {
fn from(value: nssa::PublicKey) -> Self {
impl From<nssa_core::PublicKey> for FfiPublicAccountKey {
fn from(value: nssa_core::PublicKey) -> Self {
Self {
public_key: FfiBytes32::from_bytes(*value.value()),
}
}
}
impl TryFrom<&FfiPublicAccountKey> for nssa::PublicKey {
impl TryFrom<&FfiPublicAccountKey> for nssa_core::PublicKey {
type Error = WalletFfiError;
fn try_from(value: &FfiPublicAccountKey) -> Result<Self, Self::Error> {

View File

@ -86,6 +86,7 @@ impl WalletChainStore {
data.account_id,
PrivateBundle {
key_chain: data.key_chain,
identifier: data.identifier,
account: data.account,
},
);
@ -136,6 +137,7 @@ impl WalletChainStore {
data.account_id,
PrivateBundle {
key_chain: data.key_chain,
identifier: data.identifier,
account,
},
);
@ -211,7 +213,7 @@ impl WalletChainStore {
.private_key_tree
.key_map
.entry(chain_index.clone())
.and_modify(|data| data.value.1 = account)
.and_modify(|data| data.value.account = account)
});
}
}

View File

@ -2,7 +2,8 @@ use anyhow::{Context as _, Result};
use clap::Subcommand;
use itertools::Itertools as _;
use key_protocol::key_management::key_tree::chain_index::ChainIndex;
use nssa::{Account, PublicKey, program::Program};
use nssa::{Account, program::Program};
use nssa_core::PublicKey;
use sequencer_service_rpc::RpcClient as _;
use token_core::{TokenDefinition, TokenHolding};

View File

@ -193,6 +193,7 @@ pub fn produce_data_for_storage(
InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData {
account_id: *account_id,
account: bundle.account.clone(),
identifier: bundle.identifier,
key_chain: bundle.key_chain.clone(),
}))
.into(),

View File

@ -287,7 +287,7 @@ impl WalletCore {
pub fn get_account_public_signing_key(
&self,
account_id: AccountId,
) -> Option<&nssa::PrivateKey> {
) -> Option<&nssa_core::PrivateKey> {
self.storage
.user_data
.get_pub_account_signing_key(account_id)
@ -305,7 +305,10 @@ impl WalletCore {
pub fn get_private_account_commitment(&self, account_id: AccountId) -> Option<Commitment> {
let bundle = self.storage.user_data.get_private_account(account_id)?;
Some(Commitment::new(
&bundle.key_chain.nullifier_public_key,
&AccountId::private_account_id(
&bundle.key_chain.nullifier_public_key,
bundle.identifier,
),
&bundle.account,
))
}
@ -393,6 +396,12 @@ impl WalletCore {
)?;
let private_account_keys = acc_manager.private_account_keys();
let private_account_identifiers =
acc_manager.private_account_identifiers(&private_account_keys);
// TODO: here. This is the function I want to use
// Okay. Now I have the "corrected" identifiers but NOT correct account_ids! (Marvin)
// -> So, we need to update AccountIds with these identifiers
let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove(
pre_states,
instruction_data,
@ -402,6 +411,7 @@ impl WalletCore {
.map(|keys| (keys.npk.clone(), keys.ssk))
.collect::<Vec<_>>(),
acc_manager.private_account_auth(),
private_account_identifiers.clone(), // TODO: when was this done? Marvin
acc_manager.private_account_membership_proofs(),
&program.to_owned(),
)
@ -413,7 +423,14 @@ impl WalletCore {
Vec::from_iter(acc_manager.public_account_nonces()),
private_account_keys
.iter()
.map(|keys| (keys.npk.clone(), keys.vpk.clone(), keys.epk.clone()))
.zip(private_account_identifiers)
.map(|(keys, identifier)| {
(
AccountId::private_account_id(&keys.npk.clone(), identifier),
keys.vpk.clone(),
keys.epk.clone(),
)
})
.collect(),
output,
)
@ -495,6 +512,7 @@ impl WalletCore {
acc_account_id,
PrivateBundle {
key_chain,
identifier: _,
account: _,
},
)| (*acc_account_id, key_chain, None),
@ -503,7 +521,7 @@ impl WalletCore {
|(chain_index, keys_node)| {
(
keys_node.account_id(),
&keys_node.value.0,
&keys_node.value.key_chain,
chain_index.index(),
)
},
@ -511,8 +529,9 @@ impl WalletCore {
let affected_accounts = private_account_key_chains
.flat_map(|(acc_account_id, key_chain, index)| {
// Why index? Marvin
let view_tag = EncryptedAccountData::compute_view_tag(
&key_chain.nullifier_public_key,
&acc_account_id,
&key_chain.viewing_public_key,
);

View File

@ -1,9 +1,9 @@
use anyhow::Result;
use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
use nssa::{AccountId, PrivateKey};
use nssa::AccountId;
use nssa_core::{
MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
account::{AccountWithMetadata, Nonce},
MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivateKey, SharedSecretKey,
account::{AccountWithMetadata, Identifier, Nonce},
encryption::{EphemeralPublicKey, ViewingPublicKey},
};
@ -81,10 +81,13 @@ impl AccountManager {
(State::Private(pre), mask)
}
PrivacyPreservingAccount::PrivateForeign { npk, vpk } => {
let account_id = AccountId::private_account_id(&npk, Identifier::default()); //Okay. This is being used a flag currently (Marvin) trying something
let acc = nssa_core::account::Account::default();
let auth_acc = AccountWithMetadata::new(acc, false, &npk);
let auth_acc = AccountWithMetadata::new(acc, false, account_id);
let pre = AccountPreparedData {
identifier: Identifier::default(), // TODO: ugh. Marvin
nsk: None,
npk,
vpk,
@ -136,12 +139,13 @@ impl AccountManager {
.filter_map(|state| match state {
State::Private(pre) => {
let eph_holder = EphemeralKeyHolder::new(&pre.npk);
let epk = eph_holder.generate_ephemeral_public_key();
Some(PrivateAccountKeys {
npk: pre.npk.clone(),
ssk: eph_holder.calculate_shared_secret_sender(&pre.vpk),
vpk: pre.vpk.clone(),
epk: eph_holder.generate_ephemeral_public_key(),
epk,
})
}
State::Public { .. } => None,
@ -149,6 +153,27 @@ impl AccountManager {
.collect()
}
pub fn private_account_identifiers(
&self,
private_account_keys: &Vec<PrivateAccountKeys>,
) -> Vec<Identifier> {
self.states
.iter()
.zip(private_account_keys)
.filter_map(|(state, key)| match state {
State::Private(pre) => {
if pre.identifier == Identifier::default() {
// Some(Identifier::default()) ATA works here ATA issue (Marvin)
Some(Identifier::private_identifier(&key.epk, 0)) //TODO: currently using a placeholder index (Marvin)
} else {
Some(pre.identifier)
}
}
State::Public { .. } => None,
})
.collect()
}
pub fn private_account_auth(&self) -> Vec<NullifierSecretKey> {
self.states
.iter()
@ -191,6 +216,7 @@ impl AccountManager {
}
struct AccountPreparedData {
identifier: Identifier,
nsk: Option<NullifierSecretKey>,
npk: NullifierPublicKey,
vpk: ViewingPublicKey,
@ -208,6 +234,7 @@ async fn private_acc_preparation(
let from_keys = from_bundle.key_chain;
let from_acc = from_bundle.account;
let from_identifier = from_bundle.identifier;
let nsk = from_keys.private_key_holder.nullifier_secret_key;
@ -222,9 +249,10 @@ async fn private_acc_preparation(
// TODO: Technically we could allow unauthorized owned accounts, but currently we don't have
// support from that in the wallet.
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, &from_npk);
let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, account_id);
Ok(AccountPreparedData {
identifier: from_identifier,
nsk: Some(nsk),
npk: from_npk,
vpk: from_vpk,