DAO::propose(): goodbye nullifiers, hello SMT

This commit is contained in:
zero
2024-04-01 15:51:52 +02:00
parent cb821b651e
commit fbe13ed480
28 changed files with 283 additions and 109 deletions

View File

@@ -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:

View File

@@ -16,13 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<MerkleNode>,
pub money_null_smt: &'a SmtMemoryFp,
pub signature_secret: SecretKey,
}
pub struct DaoProposeCall {
pub inputs: Vec<DaoProposeStakeInput>,
pub struct DaoProposeCall<'a> {
pub inputs: Vec<DaoProposeStakeInput<'a>>,
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);
}

View File

@@ -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,
};

View File

@@ -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 &params.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

View File

@@ -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<DaoError> 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),
}
}
}

View File

@@ -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;

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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), &[])?;

View File

@@ -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), &[])?;

View File

@@ -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), &[])?;

View File

@@ -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,

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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![],

View File

@@ -163,6 +163,12 @@ impl TestHarness {
) -> Result<Vec<OwnCoin>> {
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()));

View File

@@ -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

View File

@@ -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()

View File

@@ -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
}

View File

@@ -192,7 +192,7 @@ pub(crate) fn db_init(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, 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.

View File

@@ -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<Env>, ptr: WasmPtr<u8>, 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!(

View File

@@ -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!(

View File

@@ -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 {

View File

@@ -44,7 +44,6 @@ pub mod crypto;
/// Merkle
pub mod merkle;
pub use merkle::merkle_add;
/// Transaction structure
pub mod tx;

View File

@@ -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)
}
}