contract/consensus: use new ConsensusBurn proof in ConsensusUnstake

This commit is contained in:
aggstam
2023-05-27 18:49:05 +03:00
parent b9c817fce8
commit 1615ea1e75
13 changed files with 425 additions and 269 deletions

View File

@@ -23,16 +23,32 @@ use darkfi::{
zkas::ZkBinary,
Result,
};
use darkfi_money_contract::client::MoneyNote;
use darkfi_sdk::{
crypto::{
pasta_prelude::*, pedersen_commitment_u64, poseidon_hash, Coin, PublicKey, TokenId,
CONSENSUS_CONTRACT_ID,
pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash, Coin,
MerkleNode, MerklePosition, Nullifier, PublicKey, SecretKey, TokenId,
},
incrementalmerkletree::Hashable,
pasta::pallas,
};
use rand::rngs::OsRng;
use crate::model::ZERO;
use crate::client::ConsensusNote;
pub struct TransactionBuilderInputInfo {
pub leaf_position: MerklePosition,
pub merkle_path: Vec<MerkleNode>,
pub secret: SecretKey,
pub note: MoneyNote,
}
pub struct TransactionBuilderConsensusInputInfo {
pub leaf_position: MerklePosition,
pub merkle_path: Vec<MerkleNode>,
pub secret: SecretKey,
pub note: ConsensusNote,
}
pub struct TransactionBuilderOutputInfo {
pub value: u64,
@@ -41,7 +57,7 @@ pub struct TransactionBuilderOutputInfo {
}
pub struct ConsensusMintRevealed {
pub epoch: pallas::Base,
pub epoch: u64,
pub coin: Coin,
pub value_commit: pallas::Point,
}
@@ -49,10 +65,11 @@ pub struct ConsensusMintRevealed {
impl ConsensusMintRevealed {
pub fn to_vec(&self) -> Vec<pallas::Base> {
let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
let epoch_palas = pallas::Base::from(self.epoch);
// NOTE: It's important to keep these in the same order
// as the `constrain_instance` calls in the zkas code.
vec![self.epoch, self.coin.inner(), *valcom_coords.x(), *valcom_coords.y()]
vec![epoch_palas, self.coin.inner(), *valcom_coords.x(), *valcom_coords.y()]
}
}
@@ -73,7 +90,7 @@ pub fn create_consensus_mint_proof(
let coin =
Coin::from(poseidon_hash([pub_x, pub_y, value_pallas, epoch_pallas, serial, coin_blind]));
let public_inputs = ConsensusMintRevealed { epoch: epoch_pallas, coin, value_commit };
let public_inputs = ConsensusMintRevealed { epoch, coin, value_commit };
let prover_witnesses = vec![
Witness::Base(Value::known(pub_x)),
@@ -90,3 +107,202 @@ pub fn create_consensus_mint_proof(
Ok((proof, public_inputs))
}
pub struct ConsensusBurnRevealed {
pub nullifier: Nullifier,
pub epoch: u64,
pub signature_public: PublicKey,
pub merkle_root: MerkleNode,
pub value_commit: pallas::Point,
}
impl ConsensusBurnRevealed {
pub fn to_vec(&self) -> Vec<pallas::Base> {
let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
let sigpub_coords = self.signature_public.inner().to_affine().coordinates().unwrap();
let epoch_palas = pallas::Base::from(self.epoch);
// NOTE: It's important to keep these in the same order
// as the `constrain_instance` calls in the zkas code.
vec![
self.nullifier.inner(),
epoch_palas,
*sigpub_coords.x(),
*sigpub_coords.y(),
self.merkle_root.inner(),
*valcom_coords.x(),
*valcom_coords.y(),
]
}
}
pub fn create_consensus_burn_proof(
zkbin: &ZkBinary,
pk: &ProvingKey,
input: &TransactionBuilderConsensusInputInfo,
value_blind: pallas::Scalar,
) -> Result<(Proof, ConsensusBurnRevealed, SecretKey)> {
let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial]));
let epoch = input.note.epoch;
let epoch_pallas = pallas::Base::from(epoch);
let value_pallas = pallas::Base::from(input.note.value);
let value_commit = pedersen_commitment_u64(input.note.value, value_blind);
let public_key = PublicKey::from_secret(input.secret);
let (pub_x, pub_y) = public_key.xy();
let coin = poseidon_hash([
pub_x,
pub_y,
value_pallas,
epoch_pallas,
input.note.serial,
input.note.coin_blind,
]);
let merkle_root = {
let position: u64 = input.leaf_position.into();
let mut current = MerkleNode::from(coin);
for (level, sibling) in input.merkle_path.iter().enumerate() {
let level = level as u8;
current = if position & (1 << level) == 0 {
MerkleNode::combine(level.into(), &current, sibling)
} else {
MerkleNode::combine(level.into(), sibling, &current)
};
}
current
};
let public_inputs = ConsensusBurnRevealed {
nullifier,
epoch,
signature_public: public_key,
merkle_root,
value_commit,
};
let prover_witnesses = vec![
Witness::Base(Value::known(value_pallas)),
Witness::Base(Value::known(epoch_pallas)),
Witness::Base(Value::known(input.note.serial)),
Witness::Base(Value::known(input.note.coin_blind)),
Witness::Scalar(Value::known(value_blind)),
Witness::Base(Value::known(input.secret.inner())),
Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())),
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
];
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
Ok((proof, public_inputs, input.secret))
}
// TODO: Remove everything following
pub struct ConsensusUnstakeBurnRevealed {
pub value_commit: pallas::Point,
pub token_commit: pallas::Point,
pub nullifier: Nullifier,
pub merkle_root: MerkleNode,
pub spend_hook: pallas::Base,
pub user_data_enc: pallas::Base,
pub signature_public: PublicKey,
}
impl ConsensusUnstakeBurnRevealed {
pub fn to_vec(&self) -> Vec<pallas::Base> {
let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
let tokcom_coords = self.token_commit.to_affine().coordinates().unwrap();
let sigpub_coords = self.signature_public.inner().to_affine().coordinates().unwrap();
// NOTE: It's important to keep these in the same order
// as the `constrain_instance` calls in the zkas code.
vec![
self.nullifier.inner(),
*valcom_coords.x(),
*valcom_coords.y(),
*tokcom_coords.x(),
*tokcom_coords.y(),
self.merkle_root.inner(),
self.user_data_enc,
*sigpub_coords.x(),
*sigpub_coords.y(),
]
}
}
pub fn create_unstake_burn_proof(
zkbin: &ZkBinary,
pk: &ProvingKey,
input: &TransactionBuilderInputInfo,
value_blind: pallas::Scalar,
token_blind: pallas::Scalar,
user_data_blind: pallas::Base,
signature_secret: SecretKey,
) -> Result<(Proof, ConsensusUnstakeBurnRevealed)> {
let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial]));
let public_key = PublicKey::from_secret(input.secret);
let (pub_x, pub_y) = public_key.xy();
let signature_public = PublicKey::from_secret(signature_secret);
let coin = poseidon_hash([
pub_x,
pub_y,
pallas::Base::from(input.note.value),
input.note.token_id.inner(),
input.note.serial,
input.note.spend_hook,
input.note.user_data,
input.note.coin_blind,
]);
let merkle_root = {
let position: u64 = input.leaf_position.into();
let mut current = MerkleNode::from(coin);
for (level, sibling) in input.merkle_path.iter().enumerate() {
let level = level as u8;
current = if position & (1 << level) == 0 {
MerkleNode::combine(level.into(), &current, sibling)
} else {
MerkleNode::combine(level.into(), sibling, &current)
};
}
current
};
let user_data_enc = poseidon_hash([input.note.user_data, user_data_blind]);
let value_commit = pedersen_commitment_u64(input.note.value, value_blind);
let token_commit = pedersen_commitment_base(input.note.token_id.inner(), token_blind);
let public_inputs = ConsensusUnstakeBurnRevealed {
value_commit,
token_commit,
nullifier,
merkle_root,
spend_hook: input.note.spend_hook,
user_data_enc,
signature_public,
};
let prover_witnesses = vec![
Witness::Base(Value::known(pallas::Base::from(input.note.value))),
Witness::Base(Value::known(input.note.token_id.inner())),
Witness::Scalar(Value::known(value_blind)),
Witness::Scalar(Value::known(token_blind)),
Witness::Base(Value::known(input.note.serial)),
Witness::Base(Value::known(input.note.spend_hook)),
Witness::Base(Value::known(input.note.user_data)),
Witness::Base(Value::known(user_data_blind)),
Witness::Base(Value::known(input.note.coin_blind)),
Witness::Base(Value::known(input.secret.inner())),
Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())),
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
Witness::Base(Value::known(signature_secret.inner())),
];
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
Ok((proof, public_inputs))
}

View File

@@ -23,20 +23,20 @@ use darkfi::{
zkas::ZkBinary,
Result,
};
use darkfi_money_contract::{client::MoneyNote, model::ClearInput};
use darkfi_money_contract::model::ClearInput;
use darkfi_sdk::{
crypto::{
note::AeadEncryptedNote, pasta_prelude::*, Keypair, PublicKey, CONSENSUS_CONTRACT_ID,
DARK_TOKEN_ID,
},
crypto::{note::AeadEncryptedNote, pasta_prelude::*, Keypair, PublicKey, DARK_TOKEN_ID},
pasta::pallas,
};
use log::{debug, info};
use rand::rngs::OsRng;
use crate::{
client::common::{create_consensus_mint_proof, TransactionBuilderOutputInfo},
model::{ConsensusGenesisStakeParamsV1, ConsensusOutput, ZERO},
client::{
common::{create_consensus_mint_proof, TransactionBuilderOutputInfo},
ConsensusNote,
},
model::{ConsensusGenesisStakeParamsV1, ConsensusOutput},
};
pub struct ConsensusGenesisStakeCallDebris {
@@ -96,17 +96,7 @@ impl ConsensusGenesisStakeCallBuilder {
)?;
// Encrypted note
let note = MoneyNote {
serial,
value: output.value,
token_id: output.token_id,
spend_hook: CONSENSUS_CONTRACT_ID.inner(),
user_data: ZERO,
coin_blind,
value_blind,
token_blind,
memo: vec![],
};
let note = ConsensusNote { serial, value: output.value, epoch, coin_blind, value_blind };
let encrypted_note = AeadEncryptedNote::encrypt(&note, &output.public_key, &mut OsRng)?;

View File

@@ -25,6 +25,15 @@
//! the necessary objects provided by the caller. This is intentional, so we
//! are able to abstract away any wallet interfaces to client implementations.
use darkfi_money_contract::client::{MoneyNote, OwnCoin};
use darkfi_sdk::{
crypto::{pasta_prelude::Field, Coin, MerklePosition, Nullifier, SecretKey, DARK_TOKEN_ID},
pasta::pallas,
};
use darkfi_serial::{SerialDecodable, SerialEncodable};
use crate::model::ZERO;
/// Common functions
pub(crate) mod common;
@@ -41,3 +50,61 @@ pub mod proposal_v1;
/// `Consensus::UnstakeV1` API
pub mod unstake_v1;
/// `ConsensusNote` holds the inner attributes of a `Coin`.
#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
pub struct ConsensusNote {
/// Serial number of the coin, used for the nullifier
pub serial: pallas::Base,
/// Value of the coin
pub value: u64,
/// Epoch the coin was minted
pub epoch: u64,
/// Blinding factor for the coin bulla
pub coin_blind: pallas::Base,
/// Blinding factor for the value pedersen commitment
pub value_blind: pallas::Scalar,
}
impl From<ConsensusNote> for MoneyNote {
fn from(consensus_note: ConsensusNote) -> Self {
MoneyNote {
serial: consensus_note.serial,
value: consensus_note.value,
token_id: *DARK_TOKEN_ID,
spend_hook: ZERO,
user_data: ZERO,
coin_blind: consensus_note.coin_blind,
value_blind: consensus_note.value_blind,
token_blind: pallas::Scalar::zero(),
memo: vec![],
}
}
}
/// `ConsensusOwnCoin` is a representation of `Coin` with its respective metadata.
#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
pub struct ConsensusOwnCoin {
/// The coin hash
pub coin: Coin,
/// The attached `ConsensusNote`
pub note: ConsensusNote,
/// Coin's secret key
pub secret: SecretKey,
/// Coin's nullifier
pub nullifier: Nullifier,
/// Coin's leaf position in the Merkle tree of coins
pub leaf_position: MerklePosition,
}
impl From<ConsensusOwnCoin> for OwnCoin {
fn from(consensus_own_coin: ConsensusOwnCoin) -> Self {
OwnCoin {
coin: consensus_own_coin.coin,
note: consensus_own_coin.note.into(),
secret: consensus_own_coin.secret,
nullifier: consensus_own_coin.nullifier,
leaf_position: consensus_own_coin.leaf_position,
}
}
}

View File

@@ -42,8 +42,8 @@ use rand::rngs::OsRng;
use crate::{
client::{
common::{create_unstake_burn_proof, TransactionBuilderInputInfo as UnstakeTBII},
stake_v1::{TransactionBuilderOutputInfo as StakeTBOI, TransactionBuilderOutputInfo},
unstake_v1::{create_unstake_burn_proof, TransactionBuilderInputInfo as UnstakeTBII},
},
model::{
ConsensusProposalBurnParamsV1, ConsensusProposalMintParamsV1,

View File

@@ -19,25 +19,24 @@
//! This API is crufty. Please rework it into something nice to read and nice to use.
use darkfi::{
zk::{halo2::Value, Proof, ProvingKey, Witness, ZkCircuit},
zk::{Proof, ProvingKey},
zkas::ZkBinary,
Result,
};
use darkfi_money_contract::{
client::{MoneyNote, OwnCoin},
model::{ConsensusUnstakeParamsV1, Input},
};
use darkfi_money_contract::model::{ConsensusUnstakeParamsV1, UnstakeInput};
use darkfi_sdk::{
crypto::{
pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash,
MerkleNode, MerklePosition, MerkleTree, Nullifier, PublicKey, SecretKey, DARK_TOKEN_ID,
},
incrementalmerkletree::{Hashable, Tree},
crypto::{pasta_prelude::*, MerkleTree, SecretKey},
incrementalmerkletree::Tree,
pasta::pallas,
};
use log::{debug, info};
use rand::rngs::OsRng;
use crate::client::{
common::{create_consensus_burn_proof, TransactionBuilderConsensusInputInfo},
ConsensusOwnCoin,
};
pub struct ConsensusUnstakeCallDebris {
pub params: ConsensusUnstakeParamsV1,
pub proofs: Vec<Proof>,
@@ -45,54 +44,15 @@ pub struct ConsensusUnstakeCallDebris {
pub value_blind: pallas::Scalar,
}
pub struct ConsensusUnstakeBurnRevealed {
pub value_commit: pallas::Point,
pub token_commit: pallas::Point,
pub nullifier: Nullifier,
pub merkle_root: MerkleNode,
pub spend_hook: pallas::Base,
pub user_data_enc: pallas::Base,
pub signature_public: PublicKey,
}
impl ConsensusUnstakeBurnRevealed {
pub fn to_vec(&self) -> Vec<pallas::Base> {
let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
let tokcom_coords = self.token_commit.to_affine().coordinates().unwrap();
let sigpub_coords = self.signature_public.inner().to_affine().coordinates().unwrap();
// NOTE: It's important to keep these in the same order
// as the `constrain_instance` calls in the zkas code.
vec![
self.nullifier.inner(),
*valcom_coords.x(),
*valcom_coords.y(),
*tokcom_coords.x(),
*tokcom_coords.y(),
self.merkle_root.inner(),
self.user_data_enc,
*sigpub_coords.x(),
*sigpub_coords.y(),
]
}
}
pub struct TransactionBuilderInputInfo {
pub leaf_position: MerklePosition,
pub merkle_path: Vec<MerkleNode>,
pub secret: SecretKey,
pub note: MoneyNote,
}
/// Struct holding necessary information to build a `Consensus::UnstakeV1` contract call.
pub struct ConsensusUnstakeCallBuilder {
/// `OwnCoin` we're given to use in this builder
pub coin: OwnCoin,
/// `ConsensusOwnCoin` we're given to use in this builder
pub coin: ConsensusOwnCoin,
/// Merkle tree of coins used to create inclusion proofs
pub tree: MerkleTree,
/// `Burn_V1` zkas circuit ZkBinary
/// `ConsensusBurn_V1` zkas circuit ZkBinary
pub burn_zkbin: ZkBinary,
/// Proving key for the `Burn_V1` zk circuit
/// Proving key for the `ConsensusBurn_V1` zk circuit
pub burn_pk: ProvingKey,
}
@@ -100,13 +60,12 @@ impl ConsensusUnstakeCallBuilder {
pub fn build(&self) -> Result<ConsensusUnstakeCallDebris> {
debug!("Building Consensus::UnstakeV1 contract call");
assert!(self.coin.note.value != 0);
assert!(self.coin.note.token_id == *DARK_TOKEN_ID);
debug!("Building anonymous input");
let leaf_position = self.coin.leaf_position;
let root = self.tree.root(0).unwrap();
let merkle_path = self.tree.authentication_path(leaf_position, &root).unwrap();
let input = TransactionBuilderInputInfo {
let input = TransactionBuilderConsensusInputInfo {
leaf_position,
merkle_path,
secret: self.coin.secret,
@@ -115,32 +74,20 @@ impl ConsensusUnstakeCallBuilder {
debug!("Finished building input");
let value_blind = pallas::Scalar::random(&mut OsRng);
let token_blind = pallas::Scalar::random(&mut OsRng);
let signature_secret = SecretKey::random(&mut OsRng);
let user_data_blind = pallas::Base::random(&mut OsRng);
info!("Creating unstake burn proof for input");
let (proof, public_inputs) = create_unstake_burn_proof(
&self.burn_zkbin,
&self.burn_pk,
&input,
value_blind,
token_blind,
user_data_blind,
signature_secret,
)?;
let (proof, public_inputs, signature_secret) =
create_consensus_burn_proof(&self.burn_zkbin, &self.burn_pk, &input, value_blind)?;
let input = Input {
let input = UnstakeInput {
epoch: self.coin.note.epoch,
value_commit: public_inputs.value_commit,
token_commit: public_inputs.token_commit,
nullifier: public_inputs.nullifier,
merkle_root: public_inputs.merkle_root,
spend_hook: public_inputs.spend_hook,
user_data_enc: public_inputs.user_data_enc,
signature_public: public_inputs.signature_public,
};
// We now fill this with necessary stuff
let params = ConsensusUnstakeParamsV1 { token_blind, input };
let params = ConsensusUnstakeParamsV1 { input };
let proofs = vec![proof];
// Now we should have all the params, zk proof, signature secret and token blind.
@@ -149,79 +96,3 @@ impl ConsensusUnstakeCallBuilder {
Ok(debris)
}
}
pub fn create_unstake_burn_proof(
zkbin: &ZkBinary,
pk: &ProvingKey,
input: &TransactionBuilderInputInfo,
value_blind: pallas::Scalar,
token_blind: pallas::Scalar,
user_data_blind: pallas::Base,
signature_secret: SecretKey,
) -> Result<(Proof, ConsensusUnstakeBurnRevealed)> {
let nullifier = Nullifier::from(poseidon_hash([input.secret.inner(), input.note.serial]));
let public_key = PublicKey::from_secret(input.secret);
let (pub_x, pub_y) = public_key.xy();
let signature_public = PublicKey::from_secret(signature_secret);
let coin = poseidon_hash([
pub_x,
pub_y,
pallas::Base::from(input.note.value),
input.note.token_id.inner(),
input.note.serial,
input.note.spend_hook,
input.note.user_data,
input.note.coin_blind,
]);
let merkle_root = {
let position: u64 = input.leaf_position.into();
let mut current = MerkleNode::from(coin);
for (level, sibling) in input.merkle_path.iter().enumerate() {
let level = level as u8;
current = if position & (1 << level) == 0 {
MerkleNode::combine(level.into(), &current, sibling)
} else {
MerkleNode::combine(level.into(), sibling, &current)
};
}
current
};
let user_data_enc = poseidon_hash([input.note.user_data, user_data_blind]);
let value_commit = pedersen_commitment_u64(input.note.value, value_blind);
let token_commit = pedersen_commitment_base(input.note.token_id.inner(), token_blind);
let public_inputs = ConsensusUnstakeBurnRevealed {
value_commit,
token_commit,
nullifier,
merkle_root,
spend_hook: input.note.spend_hook,
user_data_enc,
signature_public,
};
let prover_witnesses = vec![
Witness::Base(Value::known(pallas::Base::from(input.note.value))),
Witness::Base(Value::known(input.note.token_id.inner())),
Witness::Scalar(Value::known(value_blind)),
Witness::Scalar(Value::known(token_blind)),
Witness::Base(Value::known(input.note.serial)),
Witness::Base(Value::known(input.note.spend_hook)),
Witness::Base(Value::known(input.note.user_data)),
Witness::Base(Value::known(user_data_blind)),
Witness::Base(Value::known(input.note.coin_blind)),
Witness::Base(Value::known(input.secret.inner())),
Witness::Uint32(Value::known(u64::from(input.leaf_position).try_into().unwrap())),
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
Witness::Base(Value::known(signature_secret.inner())),
];
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
let proof = Proof::create(pk, &[circuit], &public_inputs.to_vec(), &mut OsRng)?;
Ok((proof, public_inputs))
}

View File

@@ -92,15 +92,17 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
// order to be able to verify the circuits being bundled and enforcing
// a specific tree inside sled, and also creation of VerifyingKey.
let money_mint_v1_bincode = include_bytes!("../../money/proof/mint_v1.zk.bin");
let consensus_mint_v1_bincode = include_bytes!("../proof/consensus_mint_v1.zk.bin");
let money_burn_v1_bincode = include_bytes!("../../money/proof/burn_v1.zk.bin");
let consensus_mint_v1_bincode = include_bytes!("../proof/consensus_mint_v1.zk.bin");
let consensus_burn_v1_bincode = include_bytes!("../proof/consensus_burn_v1.zk.bin");
let proposal_reward_v1_bincode = include_bytes!("../proof/proposal_reward_v1.zk.bin");
let proposal_mint_v1_bincode = include_bytes!("../proof/proposal_mint_v1.zk.bin");
// For that, we use `zkas_db_set` and pass in the bincode.
zkas_db_set(&money_mint_v1_bincode[..])?;
zkas_db_set(&consensus_mint_v1_bincode[..])?;
zkas_db_set(&money_burn_v1_bincode[..])?;
zkas_db_set(&consensus_mint_v1_bincode[..])?;
zkas_db_set(&consensus_burn_v1_bincode[..])?;
zkas_db_set(&proposal_reward_v1_bincode[..])?;
zkas_db_set(&proposal_mint_v1_bincode[..])?;

View File

@@ -20,13 +20,10 @@ use darkfi_money_contract::{
error::MoneyError,
model::{ConsensusUnstakeParamsV1, ConsensusUnstakeUpdateV1, MoneyUnstakeParamsV1},
CONSENSUS_CONTRACT_COIN_ROOTS_TREE, CONSENSUS_CONTRACT_NULLIFIERS_TREE,
MONEY_CONTRACT_ZKAS_BURN_NS_V1,
CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1,
};
use darkfi_sdk::{
crypto::{
pasta_prelude::*, pedersen_commitment_base, ContractId, CONSENSUS_CONTRACT_ID,
DARK_TOKEN_ID, MONEY_CONTRACT_ID,
},
crypto::{pasta_prelude::*, ContractId, MONEY_CONTRACT_ID},
db::{db_contains_key, db_lookup, db_set},
error::{ContractError, ContractResult},
msg,
@@ -35,7 +32,7 @@ use darkfi_sdk::{
};
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
use crate::{model::ZERO, ConsensusFunction};
use crate::ConsensusFunction;
/// `get_metadata` function for `Consensus::UnstakeV1`
pub(crate) fn consensus_unstake_get_metadata_v1(
@@ -55,24 +52,22 @@ pub(crate) fn consensus_unstake_get_metadata_v1(
// Grab the pedersen commitments and signature pubkeys from the
// anonymous input
let value_coords = input.value_commit.to_affine().coordinates().unwrap();
let token_coords = input.token_commit.to_affine().coordinates().unwrap();
let (sig_x, sig_y) = input.signature_public.xy();
let epoch_palas = pallas::Base::from(input.epoch);
// It is very important that these are in the same order as the
// `constrain_instance` calls in the zkas code.
// Otherwise verification will fail.
zk_public_inputs.push((
MONEY_CONTRACT_ZKAS_BURN_NS_V1.to_string(),
CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1.to_string(),
vec![
input.nullifier.inner(),
*value_coords.x(),
*value_coords.y(),
*token_coords.x(),
*token_coords.y(),
input.merkle_root.inner(),
input.user_data_enc,
epoch_palas,
sig_x,
sig_y,
input.merkle_root.inner(),
*value_coords.x(),
*value_coords.y(),
],
));
@@ -105,12 +100,6 @@ pub(crate) fn consensus_unstake_process_instruction_v1(
msg!("[ConsensusUnstakeV1] Validating anonymous input");
let input = &params.input;
// Only native token can be unstaked
if input.token_commit != pedersen_commitment_base(DARK_TOKEN_ID.inner(), params.token_blind) {
msg!("[ConsensusUnstakeV1] Error: Input used non-native token");
return Err(MoneyError::StakeInputNonNativeToken.into())
}
// The Merkle root is used to know whether this is a coin that
// existed in a previous state.
if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
@@ -137,17 +126,6 @@ pub(crate) fn consensus_unstake_process_instruction_v1(
return Err(MoneyError::UnstakeNextCallNotMoneyContract.into())
}
// Check if spend hook is set and its correctness
if input.spend_hook == ZERO {
msg!("[ConsensusUnstakeV1] Error: Missing spend hook");
return Err(MoneyError::StakeMissingSpendHook.into())
}
if input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
msg!("[ConsensusUnstakeV1] Error: Spend hook is not consensus contract");
return Err(MoneyError::UnstakeSpendHookNotConsensusContract.into())
}
// Verify next call corresponds to Money::UnstakeV1 (0x07)
if next.data[0] != 0x07 {
msg!("[ConsensusUnstakeV1] Error: Next call function mismatch");

View File

@@ -106,48 +106,50 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
// Gather new staked owncoin
let alice_staked_oc =
th.gather_consensus_owncoin(Holder::Alice, genesis_stake_params.output)?;
th.gather_consensus_owncoin(Holder::Alice, genesis_stake_params.output, None)?;
// Verify values match
assert!(ALICE_INITIAL == alice_staked_oc.note.value);
/*
// We simulate the proposal of genesis slot
let slot_checkpoint = th.get_slot_checkpoints_by_slot(current_slot).await?;
// We simulate the proposal of genesis slot
let slot_checkpoint = th.get_slot_checkpoints_by_slot(current_slot).await?;
// With alice's current coin value she can become the slot proposer,
// so she creates a proposal transaction to burn her staked coin,
// reward herself and mint the new coin.
info!(target: "consensus", "[Alice] ====================");
info!(target: "consensus", "[Alice] Building proposal tx");
info!(target: "consensus", "[Alice] ====================");
let (proposal_tx, proposal_params) =
th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?;
// With alice's current coin value she can become the slot proposer,
// so she creates a proposal transaction to burn her staked coin,
// reward herself and mint the new coin.
info!(target: "consensus", "[Alice] ====================");
info!(target: "consensus", "[Alice] Building proposal tx");
info!(target: "consensus", "[Alice] ====================");
let (proposal_tx, proposal_params) =
th.proposal(Holder::Alice, slot_checkpoint, alice_staked_oc.clone())?;
info!(target: "consensus", "[Faucet] ===========================");
info!(target: "consensus", "[Faucet] Executing Alice proposal tx");
info!(target: "consensus", "[Faucet] ===========================");
th.execute_proposal_tx(Holder::Faucet, proposal_tx.clone(), &proposal_params, current_slot)
.await?;
info!(target: "consensus", "[Faucet] ===========================");
info!(target: "consensus", "[Faucet] Executing Alice proposal tx");
info!(target: "consensus", "[Faucet] ===========================");
th.execute_proposal_tx(Holder::Faucet, proposal_tx.clone(), &proposal_params, current_slot)
.await?;
info!(target: "consensus", "[Alice] ===========================");
info!(target: "consensus", "[Alice] Executing Alice proposal tx");
info!(target: "consensus", "[Alice] ===========================");
th.execute_proposal_tx(Holder::Alice, proposal_tx, &proposal_params, current_slot).await?;
info!(target: "consensus", "[Alice] ===========================");
info!(target: "consensus", "[Alice] Executing Alice proposal tx");
info!(target: "consensus", "[Alice] ===========================");
th.execute_proposal_tx(Holder::Alice, proposal_tx, &proposal_params, current_slot).await?;
th.assert_trees();
th.assert_trees();
// Gather new staked owncoin which includes the reward
let alice_rewarded_staked_oc =
th.gather_consensus_owncoin(Holder::Alice, proposal_params.output)?;
// Gather new staked owncoin which includes the reward
let alice_rewarded_staked_oc =
th.gather_owncoin(Holder::Alice, proposal_params.output, true)?;
// Verify values match
assert!((alice_staked_oc.note.value + REWARD) == alice_rewarded_staked_oc.note.value);
// Verify values match
assert!((alice_staked_oc.note.value + REWARD) == alice_rewarded_staked_oc.note.value);
*/
let alice_rewarded_staked_oc = alice_staked_oc;
// Now Alice can unstake her owncoin
info!(target: "consensus", "[Alice] ===================");
info!(target: "consensus", "[Alice] Building unstake tx");
info!(target: "consensus", "[Alice] ===================");
let (unstake_tx, unstake_params) =
let (unstake_tx, unstake_params, unstake_secret_key) =
th.unstake_native(Holder::Alice, alice_rewarded_staked_oc.clone())?;
info!(target: "consensus", "[Faucet] ==========================");
@@ -164,7 +166,8 @@ async fn consensus_contract_genesis_stake_unstake() -> Result<()> {
th.assert_trees();
// Gather new unstaked owncoin
let alice_unstaked_oc = th.gather_owncoin(Holder::Alice, unstake_params.output, false)?;
let alice_unstaked_oc =
th.gather_owncoin(Holder::Alice, unstake_params.output, Some(unstake_secret_key))?;
// Verify values match
assert!(alice_rewarded_staked_oc.note.value == alice_unstaked_oc.note.value);

View File

@@ -35,7 +35,7 @@ use darkfi::{
use darkfi_sdk::{
crypto::{
merkle_prelude::*, poseidon_hash, Coin, Keypair, MerkleNode, MerkleTree, Nullifier,
PublicKey, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
PublicKey, SecretKey, CONSENSUS_CONTRACT_ID, DARK_TOKEN_ID, MONEY_CONTRACT_ID,
},
pasta::pallas,
ContractCall,
@@ -48,7 +48,7 @@ use darkfi_consensus_contract::{
client::{
genesis_stake_v1::ConsensusGenesisStakeCallBuilder,
proposal_v1::ConsensusProposalCallBuilder, stake_v1::ConsensusStakeCallBuilder,
unstake_v1::ConsensusUnstakeCallBuilder,
unstake_v1::ConsensusUnstakeCallBuilder, ConsensusNote, ConsensusOwnCoin,
},
model::{ConsensusGenesisStakeParamsV1, ConsensusOutput, ConsensusProposalMintParamsV1},
ConsensusFunction,
@@ -59,9 +59,9 @@ use darkfi_money_contract::{
unstake_v1::MoneyUnstakeCallBuilder, MoneyNote, OwnCoin,
},
model::{ConsensusStakeParamsV1, MoneyTransferParamsV1, MoneyUnstakeParamsV1, Output},
MoneyFunction, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1,
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
MONEY_CONTRACT_ZKAS_MINT_NS_V1,
MoneyFunction, CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1, CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1,
CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1, CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1,
MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
pub fn init_logger() {
@@ -232,6 +232,7 @@ impl ConsensusTestHarness {
mkpk!(MONEY_CONTRACT_ZKAS_MINT_NS_V1);
mkpk!(CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1);
mkpk!(MONEY_CONTRACT_ZKAS_BURN_NS_V1);
mkpk!(CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1);
mkpk!(CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1);
mkpk!(CONSENSUS_CONTRACT_ZKAS_PROPOSAL_MINT_NS_V1);
@@ -606,10 +607,11 @@ impl ConsensusTestHarness {
pub fn unstake_native(
&mut self,
holder: Holder,
staked_oc: OwnCoin,
) -> Result<(Transaction, MoneyUnstakeParamsV1)> {
staked_oc: ConsensusOwnCoin,
) -> Result<(Transaction, MoneyUnstakeParamsV1, SecretKey)> {
let wallet = self.holders.get_mut(&holder).unwrap();
let (burn_pk, burn_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
let (burn_pk, burn_zkbin) =
self.proving_keys.get(&CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
let tx_action_benchmark = self.tx_action_benchmarks.get_mut(&TxAction::Unstake).unwrap();
let timer = Instant::now();
@@ -636,10 +638,8 @@ impl ConsensusTestHarness {
// Building Money::Unstake params
let money_unstake_call_debris = MoneyUnstakeCallBuilder {
coin: staked_oc,
recipient: wallet.keypair.public,
coin: staked_oc.into(),
value_blind: consensus_unstake_value_blind,
token_blind: consensus_unstake_params.token_blind,
nullifier: consensus_unstake_params.input.nullifier,
merkle_root: consensus_unstake_params.input.merkle_root,
signature_public: consensus_unstake_params.input.signature_public,
@@ -675,7 +675,7 @@ impl ConsensusTestHarness {
let size = ::std::mem::size_of_val(&*base58);
tx_action_benchmark.broadcasted_sizes.push(size);
Ok((unstake_tx, money_unstake_params))
Ok((unstake_tx, money_unstake_params, consensus_unstake_secret_key))
}
pub async fn execute_unstake_native_tx(
@@ -698,10 +698,19 @@ impl ConsensusTestHarness {
Ok(())
}
pub fn gather_owncoin(&mut self, holder: Holder, output: Output) -> Result<OwnCoin> {
pub fn gather_owncoin(
&mut self,
holder: Holder,
output: Output,
secret_key: Option<SecretKey>,
) -> Result<OwnCoin> {
let wallet = self.holders.get_mut(&holder).unwrap();
let leaf_position = wallet.merkle_tree.witness().unwrap();
let note: MoneyNote = output.note.decrypt(&wallet.keypair.secret)?;
let secret_key = match secret_key {
Some(key) => key,
None => wallet.keypair.secret,
};
let note: MoneyNote = output.note.decrypt(&secret_key)?;
let oc = OwnCoin {
coin: Coin::from(output.coin),
note: note.clone(),
@@ -717,11 +726,16 @@ impl ConsensusTestHarness {
&mut self,
holder: Holder,
output: ConsensusOutput,
) -> Result<OwnCoin> {
secret_key: Option<SecretKey>,
) -> Result<ConsensusOwnCoin> {
let wallet = self.holders.get_mut(&holder).unwrap();
let leaf_position = wallet.consensus_merkle_tree.witness().unwrap();
let note: MoneyNote = output.note.decrypt(&wallet.keypair.secret)?;
let oc = OwnCoin {
let secret_key = match secret_key {
Some(key) => key,
None => wallet.keypair.secret,
};
let note: ConsensusNote = output.note.decrypt(&secret_key)?;
let oc = ConsensusOwnCoin {
coin: Coin::from(output.coin),
note: note.clone(),
secret: wallet.keypair.secret,

View File

@@ -77,12 +77,8 @@ pub struct TransactionBuilderOutputInfo {
pub struct MoneyUnstakeCallBuilder {
/// `OwnCoin` we're given to use in this builder
pub coin: OwnCoin,
/// Recipient's public key
pub recipient: PublicKey,
/// Blinding factor for value commitment
pub value_blind: pallas::Scalar,
/// Blinding factor for `token_id`
pub token_blind: pallas::Scalar,
/// Revealed nullifier
pub nullifier: Nullifier,
/// Revealed Merkle root
@@ -105,13 +101,14 @@ impl MoneyUnstakeCallBuilder {
let output = TransactionBuilderOutputInfo {
value: self.coin.note.value,
token_id: self.coin.note.token_id,
public_key: self.recipient,
public_key: self.signature_public,
};
debug!("Finished building output");
let serial = pallas::Base::random(&mut OsRng);
let spend_hook = pallas::Base::zero();
let user_data_enc = pallas::Base::zero();
let token_blind = pallas::Scalar::random(&mut OsRng);
let coin_blind = pallas::Base::random(&mut OsRng);
info!("Creating unstake mint proof for output");
@@ -120,7 +117,7 @@ impl MoneyUnstakeCallBuilder {
&self.mint_pk,
&output,
self.value_blind,
self.token_blind,
token_blind,
serial,
spend_hook,
user_data_enc,
@@ -136,7 +133,7 @@ impl MoneyUnstakeCallBuilder {
user_data: user_data_enc,
coin_blind,
value_blind: self.value_blind,
token_blind: self.token_blind,
token_blind,
memo: vec![],
};
@@ -150,7 +147,7 @@ impl MoneyUnstakeCallBuilder {
};
let input = StakeInput {
token_blind: self.token_blind,
token_blind,
value_commit: public_inputs.value_commit,
nullifier: self.nullifier,
merkle_root: self.merkle_root,

View File

@@ -152,12 +152,6 @@ pub(crate) fn money_unstake_process_instruction_v1(
return Err(MoneyError::PreviousCallInputMissmatch.into())
}
// Check spend hook correctness
if previous_input.spend_hook != CONSENSUS_CONTRACT_ID.inner() {
msg!("[MoneyUnstakeV1] Error: Invoking contract call does not match spend hook in input");
return Err(MoneyError::SpendHookMismatch.into())
}
// If next spend hook is set, check its correctness
if params.spend_hook != pallas::Base::zero() {
let next_call_idx = call_idx + 1;

View File

@@ -102,6 +102,8 @@ pub const CONSENSUS_CONTRACT_COIN_MERKLE_TREE: &str = "consensus_coin_tree";
/// zkas consensus mint circuit namespace
pub const CONSENSUS_CONTRACT_ZKAS_MINT_NS_V1: &str = "ConsensusMint_V1";
/// zkas consensus burn circuit namespace
pub const CONSENSUS_CONTRACT_ZKAS_BURN_NS_V1: &str = "ConsensusBurn_V1";
/// zkas proposal reward circuit namespace
pub const CONSENSUS_CONTRACT_ZKAS_PROPOSAL_REWARD_NS_V1: &str = "ProposalReward_V1";
/// zkas proposal mint circuit namespace

View File

@@ -84,6 +84,30 @@ impl PartialEq<StakeInput> for Input {
}
}
/// Anonymous input for unstaking contract calls
#[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)]
pub struct UnstakeInput {
/// Epoch the coin was minted
pub epoch: u64,
/// Pedersen commitment for the staked coin's value
pub value_commit: pallas::Point,
/// Revealed nullifier
pub nullifier: Nullifier,
/// Revealed Merkle root
pub merkle_root: MerkleNode,
/// Public key for the signature
pub signature_public: PublicKey,
}
impl PartialEq<StakeInput> for UnstakeInput {
fn eq(&self, other: &StakeInput) -> bool {
self.value_commit == other.value_commit &&
self.nullifier == other.nullifier &&
self.merkle_root == other.merkle_root &&
self.signature_public == other.signature_public
}
}
/// A contract call's anonymous output
#[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)]
pub struct Output {
@@ -208,10 +232,8 @@ pub struct ConsensusStakeUpdateV1 {
/// Parameters for `Consensus::Unstake`
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
pub struct ConsensusUnstakeParamsV1 {
/// Blinding factor for `token_id`
pub token_blind: pallas::Scalar,
/// Anonymous input
pub input: Input,
pub input: UnstakeInput,
}
/// State update for `Consensus::Unstake`