From 607dde60d554db2b6dbf77781dfabd575a2956c8 Mon Sep 17 00:00:00 2001 From: parazyd Date: Mon, 19 Dec 2022 10:49:52 +0100 Subject: [PATCH] contract/money: Add stub for minting arbitrary tokens. --- src/contract/money/proof/mint_v1.zk | 4 +- src/contract/money/proof/token_mint_v1.zk | 74 +++++++++++++++++++++++ src/contract/money/src/lib.rs | 20 ++++++ src/sdk/src/crypto/token_id.rs | 12 ++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/contract/money/proof/token_mint_v1.zk diff --git a/src/contract/money/proof/mint_v1.zk b/src/contract/money/proof/mint_v1.zk index fb1bd36d8..42e956138 100644 --- a/src/contract/money/proof/mint_v1.zk +++ b/src/contract/money/proof/mint_v1.zk @@ -21,9 +21,9 @@ contract "Mint_V1" { Base spend_hook, # Data passed from this coin to the invoked contract Base user_data, - # Random blinding factor for value commitment + # Random blinding factor for the value commitment Scalar value_blind, - # Random blinding facfor for the token ID + # Random blinding factor for the token ID Scalar token_blind, } diff --git a/src/contract/money/proof/token_mint_v1.zk b/src/contract/money/proof/token_mint_v1.zk new file mode 100644 index 000000000..0add4f537 --- /dev/null +++ b/src/contract/money/proof/token_mint_v1.zk @@ -0,0 +1,74 @@ +constant "TokenMint_V1" { + EcFixedPointShort VALUE_COMMIT_VALUE, + EcFixedPoint VALUE_COMMIT_RANDOM, + EcFixedPointBase NULLIFIER_K, +} + +contract "TokenMint_V1" { + # Token mint authority secret + Base mint_authority, + # Token supply + Base supply, + # Fixed supply + Base fixed_supply, + # Recipient's public key x coordinate + Base rcpt_x, + # Recipient's public key y coordinate + Base rcpt_y, + # Unique serial number corresponding to this coin + Base serial, + # Random blinding factor for coin + Base coin_blind, + # Allows composing this ZK proof to invoke other contracts + Base spend_hook, + # Data passed from this coin to the invoked contract + Base user_data, + # Random blinding factor for the value commitment + Scalar value_blind, + # Random blinding factor for the token ID + Scalar token_blind, +} + +circuit "TokenMint_V1" { + # Derive public key for the mint authority + mint_public = ec_mul_base(mint_authority, NULLIFIER_K); + mint_x = ec_get_x(mint_public); + mint_y = ec_get_y(mint_public); + constrain_instance(mint_x); + constrain_instance(mint_y); + + # Derive the token ID + token_id = poseidon_hash(mint_x, mint_y); + constrain_instance(token_id); + + # Constrain whether this token has a fixed supply or not. + # In case it is, subsequent mints will not be allowed. + bool_check(fixed_supply); + constrain_instance(fixed_supply); + + # Poseidon hash of the coin + C = poseidon_hash( + rcpt_x, + rcpt_y, + supply, + token_id, + spend_hook, + user_data, + coin_blind, + ); + constrain_instance(C); + + # Pedersen commitment for the coin's value + vcv = ec_mul_short(supply, VALUE_COMMIT_VALUE); + vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM); + value_commit = ec_add(vcv, vcr); + constrain_instance(ec_get_x(value_commit)); + constrain_instance(ec_get_y(value_commit)); + + # Pedersen commitment for the token ID + tcv = ec_mul_base(token_id, NULLIFIER_K); + tcr = ec_mul(token_blind, VALUE_COMMIT_RANDOM); + token_commit = ec_add(tcv, tcr); + constrain_instance(ec_get_x(token_commit)); + constrain_instance(ec_get_y(token_commit)); +} diff --git a/src/contract/money/src/lib.rs b/src/contract/money/src/lib.rs index f729a3728..f289dd05e 100644 --- a/src/contract/money/src/lib.rs +++ b/src/contract/money/src/lib.rs @@ -47,6 +47,7 @@ pub enum MoneyFunction { OtcSwap = 0x01, Stake = 0x02, Unstake = 0x03, + Mint = 0x04, } impl TryFrom for MoneyFunction { @@ -58,6 +59,7 @@ impl TryFrom for MoneyFunction { 0x01 => Ok(Self::OtcSwap), 0x02 => Ok(Self::Stake), 0x03 => Ok(Self::Unstake), + 0x04 => Ok(Self::Mint), _ => Err(ContractError::InvalidFunction), } } @@ -84,6 +86,7 @@ darkfi_sdk::define_contract!( // These are the different sled trees that will be created pub const MONEY_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots"; pub const MONEY_CONTRACT_NULLIFIERS_TREE: &str = "nullifiers"; +pub const MONEY_CONTRACT_FIXED_SUPPLY_TREE: &str = "fixed_supply_tokens"; pub const MONEY_CONTRACT_INFO_TREE: &str = "info"; // This is a key inside the info tree @@ -94,6 +97,8 @@ pub const MONEY_CONTRACT_FAUCET_PUBKEYS: &str = "faucet_pubkeys"; pub const MONEY_CONTRACT_ZKAS_MINT_NS_V1: &str = "Mint_V1"; /// zkas burn contract namespace pub const MONEY_CONTRACT_ZKAS_BURN_NS_V1: &str = "Burn_V1"; +/// zkas token mint contract namespace +pub const MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1: &str = "TokenMint_V1"; /// This function runs when the contract is (re)deployed and initialized. #[cfg(not(feature = "no-entrypoint"))] @@ -111,6 +116,7 @@ fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult { }; let mint_v1_bincode = include_bytes!("../proof/mint_v1.zk.bin"); let burn_v1_bincode = include_bytes!("../proof/burn_v1.zk.bin"); + let token_mint_v1_bincode = include_bytes!("../proof/token_mint_v1.zk.bin"); /* TODO: Do I really want to make zkas a dependency? Yeah, in the future. For now we take anything. @@ -125,6 +131,7 @@ fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult { */ db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_MINT_NS_V1), &mint_v1_bincode[..])?; db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_BURN_NS_V1), &burn_v1_bincode[..])?; + db_set(zkas_db, &serialize(&MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1), &token_mint_v1_bincode[..])?; // Set up a database tree to hold Merkle roots let _ = match db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE) { @@ -138,6 +145,12 @@ fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult { Err(_) => db_init(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?, }; + // Set up a database tree to hold the set of fixed-supply tokens + let _ = match db_lookup(cid, MONEY_CONTRACT_FIXED_SUPPLY_TREE) { + Ok(v) => v, + Err(_) => db_init(cid, MONEY_CONTRACT_FIXED_SUPPLY_TREE)?, + }; + // Set up a database tree for arbitrary data let info_db = match db_lookup(cid, MONEY_CONTRACT_INFO_TREE) { Ok(v) => v, @@ -231,6 +244,7 @@ fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult { MoneyFunction::Stake => unimplemented!(), MoneyFunction::Unstake => unimplemented!(), + MoneyFunction::Mint => unimplemented!(), }; Ok(()) @@ -444,6 +458,11 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult { msg!("[Unstake] Entered match arm"); unimplemented!(); } + + MoneyFunction::Mint => { + msg!("[Mint] Entered match arm"); + unimplemented!(); + } } } @@ -475,5 +494,6 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult { MoneyFunction::Stake => unimplemented!(), MoneyFunction::Unstake => unimplemented!(), + MoneyFunction::Mint => unimplemented!(), } } diff --git a/src/sdk/src/crypto/token_id.rs b/src/sdk/src/crypto/token_id.rs index 381f334da..5f46854c9 100644 --- a/src/sdk/src/crypto/token_id.rs +++ b/src/sdk/src/crypto/token_id.rs @@ -17,11 +17,23 @@ */ use darkfi_serial::{SerialDecodable, SerialEncodable}; +use lazy_static::lazy_static; use pasta_curves::{group::ff::PrimeField, pallas}; use super::{poseidon_hash, PublicKey, SecretKey}; use crate::error::ContractError; +lazy_static! { + // The idea here is that 0 is not a valid x coordinate for any pallas point, + // therefore a signature cannot be produced for such IDs. This allows us to + // avoid hardcoding contract IDs for arbitrary contract deployments, because + // the contracts with 0 as their x coordinate can never have a valid signature. + + /// Native DARK token ID + pub static ref DARK_TOKEN_ID: TokenId = + TokenId::from(poseidon_hash([pallas::Base::zero(), pallas::Base::from(42)])); +} + /// TokenId represents an on-chain identifier for a certain token. #[derive(Copy, Clone, Debug, Eq, PartialEq, SerialEncodable, SerialDecodable)] pub struct TokenId(pallas::Base);