diff --git a/src/contract/dao/proof/propose-input.zk b/src/contract/dao/proof/propose-input.zk
index 93eefc002..76eef64ca 100644
--- a/src/contract/dao/proof/propose-input.zk
+++ b/src/contract/dao/proof/propose-input.zk
@@ -1,4 +1,4 @@
-k = 13;
+k = 14;
field = "pallas";
constant "ProposeInput" {
@@ -19,7 +19,10 @@ witness "ProposeInput" {
Base coin_token_blind,
Uint32 leaf_pos,
- MerklePath path,
+ MerklePath coin_path,
+
+ Base null_tree_root,
+ SparseMerklePath null_path,
Base signature_secret,
}
@@ -40,10 +43,18 @@ circuit "ProposeInput" {
);
# We need this to detect whether the above coin was already spent.
- # To avoid leaking timing & other info, we can just make a
- # money::transfer() call within the same tx.
+ # Use a SMT, and show that at this position, the leaf is ZERO
+ ZERO = witness_base(0);
+ ONE = witness_base(1);
nullifier = poseidon_hash(coin_secret, coin);
- constrain_instance(nullifier);
+ is_member = sparse_tree_is_member(
+ null_tree_root, # Expected root
+ null_path, # Path to root
+ ZERO, # Leaf value
+ nullifier # Position
+ );
+ constrain_equal_base(is_member, ONE);
+ constrain_instance(null_tree_root);
# Pedersen commitment for coin's coin_value
vcv = ec_mul_short(coin_value, VALUE_COMMIT_VALUE);
@@ -57,8 +68,8 @@ circuit "ProposeInput" {
constrain_instance(coin_token_commit);
# Merkle root
- root = merkle_root(leaf_pos, path, coin);
- constrain_instance(root);
+ merkle_coin_root = merkle_root(leaf_pos, coin_path, coin);
+ constrain_instance(merkle_coin_root);
# Finally we derive a public key for the signature and constrain
# its coordinates:
diff --git a/src/contract/dao/src/client/propose.rs b/src/contract/dao/src/client/propose.rs
index 1845f88ba..74ab75824 100644
--- a/src/contract/dao/src/client/propose.rs
+++ b/src/contract/dao/src/client/propose.rs
@@ -16,13 +16,14 @@
* along with this program. If not, see .
*/
-use darkfi_money_contract::model::{CoinAttributes, Nullifier};
+use darkfi_money_contract::model::CoinAttributes;
use darkfi_sdk::{
bridgetree,
bridgetree::Hashable,
crypto::{
note::AeadEncryptedNote, pasta_prelude::*, pedersen::pedersen_commitment_u64,
- poseidon_hash, Blind, FuncId, MerkleNode, PublicKey, ScalarBlind, SecretKey,
+ poseidon_hash, smt::SmtMemoryFp, Blind, FuncId, MerkleNode, PublicKey, ScalarBlind,
+ SecretKey,
},
pasta::pallas,
};
@@ -36,16 +37,17 @@ use darkfi::{
use crate::model::{Dao, DaoProposal, DaoProposeParams, DaoProposeParamsInput, VecAuthCallCommit};
-pub struct DaoProposeStakeInput {
+pub struct DaoProposeStakeInput<'a> {
pub secret: SecretKey,
pub note: darkfi_money_contract::client::MoneyNote,
pub leaf_position: bridgetree::Position,
pub merkle_path: Vec,
+ pub money_null_smt: &'a SmtMemoryFp,
pub signature_secret: SecretKey,
}
-pub struct DaoProposeCall {
- pub inputs: Vec,
+pub struct DaoProposeCall<'a> {
+ pub inputs: Vec>,
pub proposal: DaoProposal,
pub dao: Dao,
pub dao_leaf_position: bridgetree::Position,
@@ -53,7 +55,7 @@ pub struct DaoProposeCall {
pub dao_merkle_root: MerkleNode,
}
-impl DaoProposeCall {
+impl<'a> DaoProposeCall<'a> {
pub fn make(
self,
burn_zkbin: &ZkBinary,
@@ -80,6 +82,22 @@ impl DaoProposeCall {
let note = input.note;
let leaf_pos: u64 = input.leaf_position.into();
+ let public_key = PublicKey::from_secret(input.secret);
+ let coin = CoinAttributes {
+ public_key,
+ value: note.value,
+ token_id: note.token_id,
+ spend_hook: FuncId::none(),
+ user_data: pallas::Base::ZERO,
+ blind: note.coin_blind,
+ }
+ .to_coin();
+ let nullifier = poseidon_hash([input.secret.inner(), coin.inner()]);
+
+ let smt_null_root = input.money_null_smt.root();
+ let smt_null_path = input.money_null_smt.prove_membership(&nullifier);
+ assert!(smt_null_path.verify(&smt_null_root, &pallas::Base::ZERO, &nullifier));
+
let prover_witnesses = vec![
Witness::Base(Value::known(input.secret.inner())),
Witness::Base(Value::known(pallas::Base::from(note.value))),
@@ -91,23 +109,14 @@ impl DaoProposeCall {
Witness::Base(Value::known(gov_token_blind.inner())),
Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
+ Witness::Base(Value::known(smt_null_root)),
+ Witness::SparseMerklePath(Value::known(smt_null_path.path)),
Witness::Base(Value::known(input.signature_secret.inner())),
];
- let public_key = PublicKey::from_secret(input.secret);
- let coin = CoinAttributes {
- public_key,
- value: note.value,
- token_id: note.token_id,
- spend_hook: FuncId::none(),
- user_data: pallas::Base::ZERO,
- blind: note.coin_blind,
- }
- .to_coin();
-
// TODO: We need a generic ZkSet widget to avoid doing this all the time
- let merkle_root = {
+ let merkle_coin_root = {
let position: u64 = input.leaf_position.into();
let mut current = MerkleNode::from(coin.inner());
for (level, sibling) in input.merkle_path.iter().enumerate() {
@@ -121,8 +130,6 @@ impl DaoProposeCall {
current
};
- let nullifier: Nullifier = poseidon_hash([input.secret.inner(), coin.inner()]).into();
-
let token_commit = poseidon_hash([note.token_id.inner(), gov_token_blind.inner()]);
assert_eq!(self.dao.gov_token_id, note.token_id);
@@ -132,11 +139,11 @@ impl DaoProposeCall {
let (sig_x, sig_y) = signature_public.xy();
let public_inputs = vec![
- nullifier.inner(),
+ smt_null_root,
*value_coords.x(),
*value_coords.y(),
token_commit,
- merkle_root.inner(),
+ merkle_coin_root.inner(),
sig_x,
sig_y,
];
@@ -147,8 +154,12 @@ impl DaoProposeCall {
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)?;
proofs.push(input_proof);
- let input =
- DaoProposeParamsInput { nullifier, value_commit, merkle_root, signature_public };
+ let input = DaoProposeParamsInput {
+ value_commit,
+ merkle_coin_root,
+ smt_null_root,
+ signature_public,
+ };
inputs.push(input);
}
diff --git a/src/contract/dao/src/entrypoint/mint.rs b/src/contract/dao/src/entrypoint/mint.rs
index 19eebe629..76a87c6ac 100644
--- a/src/contract/dao/src/entrypoint/mint.rs
+++ b/src/contract/dao/src/entrypoint/mint.rs
@@ -21,7 +21,8 @@ use darkfi_sdk::{
dark_tree::DarkLeaf,
db::{db_contains_key, db_lookup, db_set},
error::{ContractError, ContractResult},
- merkle_add, msg,
+ merkle::merkle_add,
+ msg,
pasta::pallas,
ContractCall,
};
diff --git a/src/contract/dao/src/entrypoint/propose.rs b/src/contract/dao/src/entrypoint/propose.rs
index 701dfbc78..aa82cd36c 100644
--- a/src/contract/dao/src/entrypoint/propose.rs
+++ b/src/contract/dao/src/entrypoint/propose.rs
@@ -18,8 +18,7 @@
use darkfi_money_contract::{
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
- MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE,
- MONEY_CONTRACT_NULLIFIER_ROOTS_TREE,
+ MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE,
};
use darkfi_sdk::{
crypto::{contract_id::MONEY_CONTRACT_ID, pasta_prelude::*, ContractId, MerkleNode, PublicKey},
@@ -39,6 +38,7 @@ use crate::{
model::{DaoBlindAggregateVote, DaoProposalMetadata, DaoProposeParams, DaoProposeUpdate},
DaoFunction, DAO_CONTRACT_DB_DAO_MERKLE_ROOTS, DAO_CONTRACT_DB_PROPOSAL_BULLAS,
DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS, DAO_CONTRACT_ZKAS_DAO_PROPOSE_MAIN_NS,
+ PROPOSAL_SNAPSHOT_CUTOFF_LIMIT,
};
/// `get_metdata` function for `Dao::Propose`
@@ -74,11 +74,11 @@ pub(crate) fn dao_propose_get_metadata(
zk_public_inputs.push((
DAO_CONTRACT_ZKAS_DAO_PROPOSE_INPUT_NS.to_string(),
vec![
- input.nullifier.inner(),
+ input.smt_null_root,
*value_coords.x(),
*value_coords.y(),
params.token_commit,
- input.merkle_root.inner(),
+ input.merkle_coin_root.inner(),
sig_x,
sig_y,
],
@@ -120,26 +120,42 @@ pub(crate) fn dao_propose_process_instruction(
let params: DaoProposeParams = deserialize(&self_.data[1..])?;
let coin_roots_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
- let money_nullifier_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_NULLIFIERS_TREE)?;
- let mut propose_nullifiers = Vec::with_capacity(params.inputs.len());
+ let null_roots_db = db_lookup(*MONEY_CONTRACT_ID, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
for input in ¶ms.inputs {
// Check the Merkle roots for the input coins are valid
- if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
- msg!("[Dao::Propose] Error: Invalid input Merkle root: {}", input.merkle_root);
+ let Some(coin_root_data) = db_get(coin_roots_db, &serialize(&input.merkle_coin_root))?
+ else {
+ msg!(
+ "[Dao::Propose] Error: Invalid input Merkle root: {:?}",
+ input.merkle_coin_root.inner()
+ );
return Err(DaoError::InvalidInputMerkleRoot.into())
+ };
+
+ // Check the SMT roots for the input nullifiers are valid
+ let Some(null_root_data) = db_get(null_roots_db, &serialize(&input.smt_null_root))? else {
+ msg!("[Dao::Propose] Error: Invalid input SMT root: {:?}", input.smt_null_root);
+ return Err(DaoError::InvalidInputMerkleRoot.into())
+ };
+
+ // Both roots must snapshot the exact same state
+ if coin_root_data != null_root_data {
+ msg!("[Dao::Propose] Error: coin roots snapshot for {:?} does not match nulls root snapshot {:?}",
+ input.merkle_coin_root.inner(), input.smt_null_root);
+ return Err(DaoError::NonMatchingSnapshotRoots.into())
}
- // Check the coins weren't already spent
- // The nullifiers should not already exist. It is the double-spend protection.
- if propose_nullifiers.contains(&input.nullifier) ||
- db_contains_key(money_nullifier_db, &serialize(&input.nullifier))?
- {
- msg!("[Dao::Vote] Error: Coin is already spent");
- return Err(DaoError::CoinAlreadySpent.into())
+ assert_eq!(coin_root_data.len(), 32 + 2);
+ let tx_hash = &coin_root_data[0..32];
+ // Get block_height where tx_hash was confirmed
+ let tx_height = get_verifying_block_height() as u32;
+ let current_height = get_verifying_block_height() as u32;
+ if current_height - tx_height > PROPOSAL_SNAPSHOT_CUTOFF_LIMIT {
+ msg!("[Dao::Propose] Error: Snapshot is too old. Current height: {}, snapshot height: {}",
+ current_height, tx_height);
+ return Err(DaoError::SnapshotTooOld.into())
}
-
- propose_nullifiers.push(input.nullifier);
}
// Is the DAO bulla generated in the ZK proof valid
diff --git a/src/contract/dao/src/error.rs b/src/contract/dao/src/error.rs
index fae297d19..a33a1df94 100644
--- a/src/contract/dao/src/error.rs
+++ b/src/contract/dao/src/error.rs
@@ -32,6 +32,12 @@ pub enum DaoError {
#[error("Invalid input Merkle root")]
InvalidInputMerkleRoot,
+ #[error("Snapshoot roots do not match")]
+ NonMatchingSnapshotRoots,
+
+ #[error("Snapshoot is past the cutoff limit")]
+ SnapshotTooOld,
+
#[error("Invalid DAO Merkle root")]
InvalidDaoMerkleRoot,
@@ -94,24 +100,26 @@ impl From for ContractError {
DaoError::DaoAlreadyExists => Self::Custom(2),
DaoError::ProposalInputsEmpty => Self::Custom(3),
DaoError::InvalidInputMerkleRoot => Self::Custom(4),
- DaoError::InvalidDaoMerkleRoot => Self::Custom(5),
- DaoError::ProposalAlreadyExists => Self::Custom(6),
- DaoError::VoteInputsEmpty => Self::Custom(7),
- DaoError::ProposalNonexistent => Self::Custom(8),
- DaoError::ProposalEnded => Self::Custom(9),
- DaoError::CoinAlreadySpent => Self::Custom(10),
- DaoError::DoubleVote => Self::Custom(11),
- DaoError::ExecCallWrongChildCallsLen => Self::Custom(12),
- DaoError::ExecCallWrongChildCall => Self::Custom(13),
- DaoError::ExecCallInvalidFormat => Self::Custom(14),
- DaoError::ExecCallValueMismatch => Self::Custom(15),
- DaoError::VoteCommitMismatch => Self::Custom(16),
- DaoError::AuthXferSiblingWrongContractId => Self::Custom(17),
- DaoError::AuthXferSiblingWrongFunctionCode => Self::Custom(18),
- DaoError::AuthXferNonMatchingEncInputUserData => Self::Custom(19),
- DaoError::AuthXferCallNotFoundInParent => Self::Custom(20),
- DaoError::AuthXferWrongNumberOutputs => Self::Custom(21),
- DaoError::AuthXferWrongOutputCoin => Self::Custom(22),
+ DaoError::NonMatchingSnapshotRoots => Self::Custom(5),
+ DaoError::SnapshotTooOld => Self::Custom(6),
+ DaoError::InvalidDaoMerkleRoot => Self::Custom(7),
+ DaoError::ProposalAlreadyExists => Self::Custom(8),
+ DaoError::VoteInputsEmpty => Self::Custom(9),
+ DaoError::ProposalNonexistent => Self::Custom(10),
+ DaoError::ProposalEnded => Self::Custom(11),
+ DaoError::CoinAlreadySpent => Self::Custom(12),
+ DaoError::DoubleVote => Self::Custom(13),
+ DaoError::ExecCallWrongChildCallsLen => Self::Custom(14),
+ DaoError::ExecCallWrongChildCall => Self::Custom(15),
+ DaoError::ExecCallInvalidFormat => Self::Custom(16),
+ DaoError::ExecCallValueMismatch => Self::Custom(17),
+ DaoError::VoteCommitMismatch => Self::Custom(18),
+ DaoError::AuthXferSiblingWrongContractId => Self::Custom(19),
+ DaoError::AuthXferSiblingWrongFunctionCode => Self::Custom(20),
+ DaoError::AuthXferNonMatchingEncInputUserData => Self::Custom(21),
+ DaoError::AuthXferCallNotFoundInParent => Self::Custom(22),
+ DaoError::AuthXferWrongNumberOutputs => Self::Custom(23),
+ DaoError::AuthXferWrongOutputCoin => Self::Custom(24),
}
}
}
diff --git a/src/contract/dao/src/lib.rs b/src/contract/dao/src/lib.rs
index d6509b374..b34b557c2 100644
--- a/src/contract/dao/src/lib.rs
+++ b/src/contract/dao/src/lib.rs
@@ -89,6 +89,9 @@ pub const DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_NS: &str = "AuthMoneyTransfe
/// zkas dao auth money_transfer encrypted coin circuit namespace
pub const DAO_CONTRACT_ZKAS_DAO_AUTH_MONEY_TRANSFER_ENC_COIN_NS: &str = "AuthMoneyTransferEncCoin";
+/// Not allowed to make proposals using snapshots with block heights older than this depth
+pub const PROPOSAL_SNAPSHOT_CUTOFF_LIMIT: u32 = 100;
+
// ANCHOR: dao-blockwindow
const BLOCK_TIME: u64 = 90;
const SECS_IN_HOUR: u64 = 60 * 60;
diff --git a/src/contract/dao/src/model.rs b/src/contract/dao/src/model.rs
index c9bfc60fa..4970444f2 100644
--- a/src/contract/dao/src/model.rs
+++ b/src/contract/dao/src/model.rs
@@ -242,11 +242,12 @@ pub struct DaoProposeParams {
// ANCHOR: dao-propose-params-input
/// Input for a DAO proposal
pub struct DaoProposeParamsInput {
- pub nullifier: Nullifier,
/// Value commitment for the input
pub value_commit: pallas::Point,
- /// Merkle root for the input's inclusion proof
- pub merkle_root: MerkleNode,
+ /// Merkle root for the input's coin inclusion proof
+ pub merkle_coin_root: MerkleNode,
+ /// SMT root for the input's nullifier exclusion proof
+ pub smt_null_root: pallas::Base,
/// Public key used for signing
pub signature_public: PublicKey,
}
diff --git a/src/contract/money/src/entrypoint.rs b/src/contract/money/src/entrypoint.rs
index c5c669988..8d88bf850 100644
--- a/src/contract/money/src/entrypoint.rs
+++ b/src/contract/money/src/entrypoint.rs
@@ -125,16 +125,29 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
zkas_db_set(&token_mint_v1_bincode[..])?;
zkas_db_set(&token_frz_v1_bincode[..])?;
+ // FIXME: Get tx hash from env
+ let tx_hash = [0u8; 32];
+ // No way to access call_idx here
+ //assert!(ix.len() > 4);
+ //let call_idx: u32 = deserialize(&ix[0..4])?;
+ let call_idx = 110u16;
+ let mut roots_value_data = Vec::with_capacity(32 + 2);
+ tx_hash.encode(&mut roots_value_data)?;
+ call_idx.encode(&mut roots_value_data)?;
+ assert_eq!(roots_value_data.len(), 32 + 2);
+
// Set up a database tree to hold Merkle roots of all coin trees
- // k=root_hash:32, v=(block_height:3, tx_idx:2, call_idx: 2)
+ // k=root_hash:32, v=(tx_hash:32, call_idx: 2)
if db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE).is_err() {
- db_init(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+ let db_coin_roots = db_init(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+ db_set(db_coin_roots, &serialize(&EMPTY_COINS_TREE_ROOT), &roots_value_data)?;
}
// Set up a database tree to hold Merkle roots of all nullifier trees
- // k=root_hash:32, v=(block_height:3, tx_idx:2, call_idx: 2)
+ // k=root_hash:32, v=(tx_hash:32, call_idx: 2)
if db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE).is_err() {
- db_init(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
+ let db_null_roots = db_init(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
+ db_set(db_null_roots, &serialize(&EMPTY_NODES_FP[0]), &roots_value_data)?;
}
// Set up a database tree to hold all coins ever seen
diff --git a/src/contract/money/src/entrypoint/genesis_mint_v1.rs b/src/contract/money/src/entrypoint/genesis_mint_v1.rs
index 2a26abd4b..82045541f 100644
--- a/src/contract/money/src/entrypoint/genesis_mint_v1.rs
+++ b/src/contract/money/src/entrypoint/genesis_mint_v1.rs
@@ -21,7 +21,8 @@ use darkfi_sdk::{
dark_tree::DarkLeaf,
db::{db_contains_key, db_lookup, db_set},
error::{ContractError, ContractResult},
- merkle_add, msg,
+ merkle::{merkle_add, sparse_merkle_insert_batch},
+ msg,
pasta::pallas,
util::get_verifying_block_height,
ContractCall,
@@ -33,7 +34,8 @@ use crate::{
model::{MoneyGenesisMintParamsV1, MoneyGenesisMintUpdateV1, DARK_TOKEN_ID},
MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
- MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+ MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE,
+ MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
/// `get_metadata` function for `Money::GenesisMintV1`
@@ -140,7 +142,19 @@ pub(crate) fn money_genesis_mint_process_update_v1(
// Grab all db handles we want to work on
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+ let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+ let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
+
+ // This will just make a snapshot to match the coins one
+ msg!("[GenesisMintV1] Updating nullifiers snapshot");
+ sparse_merkle_insert_batch(
+ info_db,
+ nullifiers_db,
+ nullifier_roots_db,
+ MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
+ &vec![],
+ )?;
msg!("[GenesisMintV1] Adding new coin to the set");
db_set(coins_db, &serialize(&update.coin), &[])?;
diff --git a/src/contract/money/src/entrypoint/pow_reward_v1.rs b/src/contract/money/src/entrypoint/pow_reward_v1.rs
index dccec8244..94f4dc794 100644
--- a/src/contract/money/src/entrypoint/pow_reward_v1.rs
+++ b/src/contract/money/src/entrypoint/pow_reward_v1.rs
@@ -22,7 +22,8 @@ use darkfi_sdk::{
dark_tree::DarkLeaf,
db::{db_contains_key, db_lookup, db_set},
error::{ContractError, ContractResult},
- merkle_add, msg,
+ merkle::{merkle_add, sparse_merkle_insert_batch},
+ msg,
pasta::pallas,
util::{get_last_block_height, get_verifying_block_height},
ContractCall,
@@ -34,7 +35,8 @@ use crate::{
model::{MoneyPoWRewardParamsV1, MoneyPoWRewardUpdateV1, DARK_TOKEN_ID},
MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
- MONEY_CONTRACT_ZKAS_MINT_NS_V1,
+ MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE,
+ MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
/// `get_metadata` function for `Money::PoWRewardV1`
@@ -165,7 +167,19 @@ pub(crate) fn money_pow_reward_process_update_v1(
// Grab all db handles we want to work on
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+ let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+ let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
+
+ // This will just make a snapshot to match the coins one
+ msg!("[PowRewardV1] Updating nullifiers snapshot");
+ sparse_merkle_insert_batch(
+ info_db,
+ nullifiers_db,
+ nullifier_roots_db,
+ MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
+ &vec![],
+ )?;
msg!("[PoWRewardV1] Adding new coin to the set");
db_set(coins_db, &serialize(&update.coin), &[])?;
diff --git a/src/contract/money/src/entrypoint/token_mint_v1.rs b/src/contract/money/src/entrypoint/token_mint_v1.rs
index e6e2efc6b..39fab3823 100644
--- a/src/contract/money/src/entrypoint/token_mint_v1.rs
+++ b/src/contract/money/src/entrypoint/token_mint_v1.rs
@@ -21,7 +21,8 @@ use darkfi_sdk::{
dark_tree::DarkLeaf,
db::{db_contains_key, db_lookup, db_set},
error::{ContractError, ContractResult},
- merkle_add, msg,
+ merkle::{merkle_add, sparse_merkle_insert_batch},
+ msg,
pasta::pallas,
ContractCall,
};
@@ -32,7 +33,8 @@ use crate::{
model::{MoneyTokenMintParamsV1, MoneyTokenMintUpdateV1},
MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
- MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+ MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE,
+ MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
};
/// `get_metadata` function for `Money::TokenMintV1`
@@ -106,7 +108,19 @@ pub(crate) fn money_token_mint_process_update_v1(
// Grab all db handles we want to work on
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
+ let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
+ let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
+
+ // This will just make a snapshot to match the coins one
+ msg!("[MintV1] Updating nullifiers snapshot");
+ sparse_merkle_insert_batch(
+ info_db,
+ nullifiers_db,
+ nullifier_roots_db,
+ MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
+ &vec![],
+ )?;
msg!("[MintV1] Adding new coin to the set");
db_set(coins_db, &serialize(&update.coin), &[])?;
diff --git a/src/contract/money/src/lib.rs b/src/contract/money/src/lib.rs
index a0dbfabcb..170c94af0 100644
--- a/src/contract/money/src/lib.rs
+++ b/src/contract/money/src/lib.rs
@@ -83,7 +83,8 @@ pub const MONEY_CONTRACT_LATEST_COIN_ROOT: &[u8] = b"last_coins_root";
pub const MONEY_CONTRACT_LATEST_NULLIFIER_ROOT: &[u8] = b"last_nullifiers_root";
pub const MONEY_CONTRACT_TOTAL_FEES_PAID: &[u8] = b"total_fees_paid";
-/// Precalculated root hash for a tree containing Fp::ZERO to save gas
+/// Precalculated root hash for a tree containing only a single Fp::ZERO coin.
+/// Used to save gas.
pub const EMPTY_COINS_TREE_ROOT: [u8; 32] = [
0xb8, 0xc1, 0x07, 0x5a, 0x80, 0xa8, 0x09, 0x65, 0xc2, 0x39, 0x8f, 0x71, 0x1f, 0xe7, 0x3e, 0x05,
0xb4, 0xed, 0xae, 0xde, 0xf1, 0x62, 0xf2, 0x61, 0xd4, 0xee, 0xd7, 0xcd, 0x72, 0x74, 0x8d, 0x17,
diff --git a/src/contract/test-harness/src/contract_deploy.rs b/src/contract/test-harness/src/contract_deploy.rs
index c0edf0886..8b2a1f3da 100644
--- a/src/contract/test-harness/src/contract_deploy.rs
+++ b/src/contract/test-harness/src/contract_deploy.rs
@@ -110,6 +110,12 @@ impl TestHarness {
}
if let Some(ref fee_params) = fee_params {
+ let nullifier = fee_params.input.nullifier.inner();
+ wallet
+ .money_null_smt
+ .insert_batch(vec![(nullifier, nullifier)])
+ .expect("smt.insert_batch()");
+
if let Some(spent_coin) = wallet
.unspent_money_coins
.iter()
diff --git a/src/contract/test-harness/src/dao_exec.rs b/src/contract/test-harness/src/dao_exec.rs
index 483b5b23f..731266726 100644
--- a/src/contract/test-harness/src/dao_exec.rs
+++ b/src/contract/test-harness/src/dao_exec.rs
@@ -274,6 +274,9 @@ impl TestHarness {
outputs.push(fee_params.output.clone());
}
+ let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
+ wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
+
for input in inputs {
if let Some(spent_coin) = wallet
.unspent_money_coins
diff --git a/src/contract/test-harness/src/dao_mint.rs b/src/contract/test-harness/src/dao_mint.rs
index d47fc8a7d..35f795c3a 100644
--- a/src/contract/test-harness/src/dao_mint.rs
+++ b/src/contract/test-harness/src/dao_mint.rs
@@ -118,6 +118,12 @@ impl TestHarness {
wallet.dao_leafs.insert(params.dao_bulla, leaf_pos);
if let Some(ref fee_params) = fee_params {
+ let nullifier = fee_params.input.nullifier.inner();
+ wallet
+ .money_null_smt
+ .insert_batch(vec![(nullifier, nullifier)])
+ .expect("smt.insert_batch()");
+
if let Some(spent_coin) = wallet
.unspent_money_coins
.iter()
diff --git a/src/contract/test-harness/src/dao_propose.rs b/src/contract/test-harness/src/dao_propose.rs
index 4a3703af4..1ff964646 100644
--- a/src/contract/test-harness/src/dao_propose.rs
+++ b/src/contract/test-harness/src/dao_propose.rs
@@ -73,6 +73,32 @@ impl TestHarness {
let signature_secret = SecretKey::random(&mut OsRng);
+ debug!("ABOUT TO SHOW DB INTERNALZZZ!!!!!");
+ {
+ let blockchain = &wallet.validator.blockchain;
+ let contracts = &blockchain.contracts;
+ let tree = contracts
+ .lookup(&blockchain.sled_db, &MONEY_CONTRACT_ID, "nullifier_roots")
+ .unwrap();
+ for kv in tree.iter() {
+ let (key, value) = kv.unwrap();
+ debug!("STATE {:?}", key);
+ debug!(" => {:?}", value);
+ }
+ }
+ debug!("[REDUX] ABOUT TO SHOW DB INTERNALZZZ!!!!!");
+ {
+ let blockchain = &wallet.validator.blockchain;
+ let contracts = &blockchain.contracts;
+ let tree =
+ contracts.lookup(&blockchain.sled_db, &MONEY_CONTRACT_ID, "coin_roots").unwrap();
+ for kv in tree.iter() {
+ let (key, value) = kv.unwrap();
+ debug!("STATE {:?}", key);
+ debug!(" => {:?}", value);
+ }
+ }
+
let input = DaoProposeStakeInput {
secret: wallet.keypair.secret,
note: propose_owncoin.note.clone(),
@@ -81,6 +107,7 @@ impl TestHarness {
.money_merkle_tree
.witness(propose_owncoin.leaf_position, 0)
.unwrap(),
+ money_null_smt: &wallet.money_null_smt,
signature_secret,
};
@@ -197,6 +224,12 @@ impl TestHarness {
wallet.dao_prop_leafs.insert(params.proposal_bulla, (prop_leaf_pos, prop_money_snapshot));
if let Some(ref fee_params) = fee_params {
+ let nullifier = fee_params.input.nullifier.inner();
+ wallet
+ .money_null_smt
+ .insert_batch(vec![(nullifier, nullifier)])
+ .expect("smt.insert_batch()");
+
if let Some(spent_coin) = wallet
.unspent_money_coins
.iter()
diff --git a/src/contract/test-harness/src/dao_vote.rs b/src/contract/test-harness/src/dao_vote.rs
index 81fcb0eb2..ddd6e2fc2 100644
--- a/src/contract/test-harness/src/dao_vote.rs
+++ b/src/contract/test-harness/src/dao_vote.rs
@@ -155,6 +155,12 @@ impl TestHarness {
}
if let Some(ref fee_params) = fee_params {
+ let nullifier = fee_params.input.nullifier.inner();
+ wallet
+ .money_null_smt
+ .insert_batch(vec![(nullifier, nullifier)])
+ .expect("smt.insert_batch()");
+
if let Some(spent_coin) = wallet
.unspent_money_coins
.iter()
diff --git a/src/contract/test-harness/src/lib.rs b/src/contract/test-harness/src/lib.rs
index 614e68bed..6a2394dfa 100644
--- a/src/contract/test-harness/src/lib.rs
+++ b/src/contract/test-harness/src/lib.rs
@@ -35,7 +35,10 @@ use darkfi_dao_contract::model::{DaoBulla, DaoProposalBulla};
use darkfi_money_contract::client::OwnCoin;
use darkfi_sdk::{
bridgetree,
- crypto::{Keypair, MerkleNode, MerkleTree},
+ crypto::{
+ smt::{MemoryStorageFp, PoseidonFp, SmtMemoryFp, EMPTY_NODES_FP},
+ Keypair, MerkleNode, MerkleTree,
+ },
pasta::pallas,
};
use darkfi_serial::{Encodable, WriteExt};
@@ -87,8 +90,8 @@ pub fn init_logger() {
// We check this error so we can execute same file tests in parallel,
// otherwise second one fails to init logger here.
if simplelog::TermLogger::init(
- simplelog::LevelFilter::Info,
- //simplelog::LevelFilter::Debug,
+ //simplelog::LevelFilter::Info,
+ simplelog::LevelFilter::Debug,
//simplelog::LevelFilter::Trace,
cfg.build(),
simplelog::TerminalMode::Mixed,
@@ -122,6 +125,8 @@ pub struct Wallet {
pub validator: ValidatorPtr,
/// Holder's instance of the Merkle tree for the `Money` contract
pub money_merkle_tree: MerkleTree,
+ /// Holder's instance of the Merkle tree for the `Money` contract
+ pub money_null_smt: SmtMemoryFp,
/// Holder's instance of the Merkle tree for the `DAO` contract (holding DAO bullas)
pub dao_merkle_tree: MerkleTree,
/// Holder's instance of the Merkle tree for the `DAO` contract (holding DAO proposals)
@@ -170,12 +175,17 @@ impl Wallet {
money_merkle_tree.append(MerkleNode::from(pallas::Base::ZERO));
money_merkle_tree.mark().unwrap();
+ let hasher = PoseidonFp::new();
+ let store = MemoryStorageFp::new();
+ let money_null_smt = SmtMemoryFp::new(store, hasher.clone(), &EMPTY_NODES_FP);
+
Ok(Self {
keypair,
token_mint_authority,
contract_deploy_authority,
validator,
money_merkle_tree,
+ money_null_smt,
dao_merkle_tree: MerkleTree::new(100),
dao_proposals_tree: MerkleTree::new(100),
unspent_money_coins: vec![],
diff --git a/src/contract/test-harness/src/money_fee.rs b/src/contract/test-harness/src/money_fee.rs
index 11ea84afc..319eddbec 100644
--- a/src/contract/test-harness/src/money_fee.rs
+++ b/src/contract/test-harness/src/money_fee.rs
@@ -163,6 +163,12 @@ impl TestHarness {
) -> Result> {
let wallet = self.holders.get_mut(holder).unwrap();
+ let nullifier = params.input.nullifier.inner();
+ wallet
+ .money_null_smt
+ .insert_batch(vec![(nullifier, nullifier)])
+ .expect("smt.insert_batch()");
+
wallet.add_transaction("money::fee", tx, block_height, self.verify_fees).await?;
wallet.money_merkle_tree.append(MerkleNode::from(params.output.coin.inner()));
diff --git a/src/contract/test-harness/src/money_otc_swap.rs b/src/contract/test-harness/src/money_otc_swap.rs
index a57502969..2bb48511e 100644
--- a/src/contract/test-harness/src/money_otc_swap.rs
+++ b/src/contract/test-harness/src/money_otc_swap.rs
@@ -201,6 +201,9 @@ impl TestHarness {
outputs.push(fee_params.output.clone());
}
+ let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
+ wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
+
for input in inputs {
if let Some(spent_coin) = wallet
.unspent_money_coins
diff --git a/src/contract/test-harness/src/money_token.rs b/src/contract/test-harness/src/money_token.rs
index 938450d38..61c82120e 100644
--- a/src/contract/test-harness/src/money_token.rs
+++ b/src/contract/test-harness/src/money_token.rs
@@ -340,6 +340,12 @@ impl TestHarness {
let mut found_owncoins = vec![];
if let Some(ref fee_params) = fee_params {
if append {
+ let nullifier = fee_params.input.nullifier.inner();
+ wallet
+ .money_null_smt
+ .insert_batch(vec![(nullifier, nullifier)])
+ .expect("smt.insert_batch()");
+
if let Some(spent_coin) = wallet
.unspent_money_coins
.iter()
diff --git a/src/contract/test-harness/src/money_transfer.rs b/src/contract/test-harness/src/money_transfer.rs
index 9a0729ec1..57befe38d 100644
--- a/src/contract/test-harness/src/money_transfer.rs
+++ b/src/contract/test-harness/src/money_transfer.rs
@@ -134,8 +134,11 @@ impl TestHarness {
inputs.push(fee_params.input.clone());
}
+ let nullifiers = inputs.iter().map(|i| i.nullifier.inner()).map(|l| (l, l)).collect();
+ wallet.money_null_smt.insert_batch(nullifiers).expect("smt.insert_batch()");
+
if append {
- for input in inputs.iter() {
+ for input in &inputs {
if let Some(spent_coin) = wallet
.unspent_money_coins
.iter()
@@ -156,7 +159,7 @@ impl TestHarness {
outputs.push(fee_params.output.clone());
}
- for output in outputs.iter() {
+ for output in &outputs {
if !append {
continue
}
diff --git a/src/runtime/import/db.rs b/src/runtime/import/db.rs
index 18f20f0b8..b362dae24 100644
--- a/src/runtime/import/db.rs
+++ b/src/runtime/import/db.rs
@@ -192,7 +192,7 @@ pub(crate) fn db_init(mut ctx: FunctionEnvMut, ptr: WasmPtr, ptr_len: u
}
/// Lookup a database handle from its name.
-/// If it does not exist, push it to the Vector of db_handles.
+/// If it exists, push it to the Vector of db_handles.
///
/// Returns the index of the DbHandle in the db_handles Vector on success.
/// Otherwise, returns an error value.
diff --git a/src/runtime/import/merkle.rs b/src/runtime/import/merkle.rs
index bc5c7b4f5..de059abcd 100644
--- a/src/runtime/import/merkle.rs
+++ b/src/runtime/import/merkle.rs
@@ -20,7 +20,7 @@ use std::io::Cursor;
use darkfi_sdk::crypto::{MerkleNode, MerkleTree};
use darkfi_serial::{serialize, Decodable, Encodable, WriteExt};
-use log::{debug, error, warn};
+use log::{debug, error};
use wasmer::{FunctionEnvMut, WasmPtr};
use super::acl::acl_allow;
@@ -156,15 +156,6 @@ pub(crate) fn merkle_add(mut ctx: FunctionEnvMut, ptr: WasmPtr, len: u3
}
};
- // Nothing to do so just return here
- if coins.is_empty() {
- warn!(
- target: "runtime::merkle::merkle_add",
- "[WASM] [{}] merkle_add(): Nothing to add! Returning.", cid,
- );
- return darkfi_sdk::entrypoint::SUCCESS
- }
-
// Make sure we've read the entire buffer
if buf_reader.position() != (len as u64) {
error!(
diff --git a/src/runtime/import/smt.rs b/src/runtime/import/smt.rs
index 828ceeb92..910687a89 100644
--- a/src/runtime/import/smt.rs
+++ b/src/runtime/import/smt.rs
@@ -24,7 +24,7 @@ use darkfi_sdk::crypto::{
};
use darkfi_serial::{serialize, Decodable, Encodable};
use halo2_proofs::pasta::pallas;
-use log::{debug, error, warn};
+use log::{debug, error};
use num_bigint::BigUint;
use wasmer::{FunctionEnvMut, WasmPtr};
@@ -201,15 +201,6 @@ pub(crate) fn sparse_merkle_insert_batch(
}
};
- // Nothing to do so just return here
- if nullifiers.is_empty() {
- warn!(
- target: "runtime::smt::sparse_merkle_insert_batch",
- "[WASM] [{}] sparse_merkle_insert_batch(): Nothing to add! Returning.", cid
- );
- return darkfi_sdk::entrypoint::SUCCESS
- }
-
// Make sure we've read the entire buffer
if buf_reader.position() != (len as u64) {
error!(
diff --git a/src/sdk/src/crypto/smt/mod.rs b/src/sdk/src/crypto/smt/mod.rs
index 3b84419d2..64e9d0045 100644
--- a/src/sdk/src/crypto/smt/mod.rs
+++ b/src/sdk/src/crypto/smt/mod.rs
@@ -167,6 +167,10 @@ impl<
/// Takes a batch of field elements, inserts these hashes into the tree,
/// and updates the Merkle root.
pub fn insert_batch(&mut self, leaves: Vec<(F, F)>) -> ContractResult {
+ if leaves.is_empty() {
+ return Ok(())
+ }
+
// Nodes that need recalculating
let mut dirty_idxs = Vec::new();
for (pos, leaf) in leaves {
diff --git a/src/sdk/src/lib.rs b/src/sdk/src/lib.rs
index a13f772f6..811fe0c30 100644
--- a/src/sdk/src/lib.rs
+++ b/src/sdk/src/lib.rs
@@ -44,7 +44,6 @@ pub mod crypto;
/// Merkle
pub mod merkle;
-pub use merkle::merkle_add;
/// Transaction structure
pub mod tx;
diff --git a/src/zk/vm_heap.rs b/src/zk/vm_heap.rs
index 85ce29874..958f3136d 100644
--- a/src/zk/vm_heap.rs
+++ b/src/zk/vm_heap.rs
@@ -119,7 +119,7 @@ macro_rules! impl_try_from {
match value {
HeapVar::$variant(v) => Ok(v),
x => {
- error!("Invalid TryFrom conversion {:?}", x);
+ error!("Expected {}, but instead got: {:?}", stringify!($variant), x);
Err(plonk::Error::Synthesis)
}
}