mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
contract/money: Spread out lib.rs into more files.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
# Circuit used to mint arbitrary coins given a mint authority secret.
|
||||
constant "TokenMint_V1" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
@@ -9,15 +10,11 @@ contract "TokenMint_V1" {
|
||||
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
|
||||
# Random blinding factor for the minted coin
|
||||
Base coin_blind,
|
||||
# Allows composing this ZK proof to invoke other contracts
|
||||
Base spend_hook,
|
||||
@@ -41,12 +38,7 @@ circuit "TokenMint_V1" {
|
||||
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
|
||||
# Poseidon hash of the minted coin
|
||||
C = poseidon_hash(
|
||||
rcpt_x,
|
||||
rcpt_y,
|
||||
|
||||
@@ -50,6 +50,9 @@ use crate::model::{
|
||||
StakedInput, StakedOutput,
|
||||
};
|
||||
|
||||
/// Client API for token minting and freezing
|
||||
pub mod token_mint;
|
||||
|
||||
// Wallet SQL table constant names. These have to represent the SQL schema.
|
||||
// TODO: They should also ideally be prefixed with the contract ID to avoid
|
||||
// collisions.
|
||||
@@ -81,6 +84,11 @@ pub const MONEY_COINS_COL_NULLIFIER: &str = "nullifier";
|
||||
pub const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
|
||||
pub const MONEY_COINS_COL_MEMO: &str = "memo";
|
||||
|
||||
pub const MONEY_TOKENS_TABLE: &str = "money_tokens";
|
||||
pub const MONEY_TOKENS_COL_SECRET: &str = "secret";
|
||||
pub const MONEY_TOKENS_COL_TOKEN_ID: &str = "token_id";
|
||||
pub const MONEY_TOKENS_COL_FROZEN: &str = "frozen";
|
||||
|
||||
pub const MONEY_ALIASES_TABLE: &str = "money_aliases";
|
||||
pub const MONEY_ALIASES_COL_ALIAS: &str = "alias";
|
||||
pub const MONEY_ALIASES_COL_TOKEN_ID: &str = "token_id";
|
||||
|
||||
97
src/contract/money/src/client/token_mint.rs
Normal file
97
src/contract/money/src/client/token_mint.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! This API is experimental. Have to evaluate the best approach still.
|
||||
|
||||
pub struct MoneyMintClient {
|
||||
pub mint_authority: SecretKey,
|
||||
pub amount: u64,
|
||||
pub recipient: PublicKey,
|
||||
pub spend_hook: pallas::Base,
|
||||
pub user_data: pallas::Base,
|
||||
}
|
||||
|
||||
impl MoneyMintClient {
|
||||
pub fn build(&self) -> Result<MoneyMintParams> {
|
||||
debug!(target: "money::client::token_mint", "Building params");
|
||||
assert!(self.amount != 0);
|
||||
|
||||
let token_id = TokenId::derive(self.mint_authority);
|
||||
let value_blind = pallas::Scalar::random(&mut OsRng);
|
||||
let token_blind = pallas::Scalar::random(&mut OsRng);
|
||||
|
||||
let serial = pallas::Base::random(&mut OsRng);
|
||||
let coin_blind = pallas::Base::random(&mut OsRng);
|
||||
|
||||
let (pub_x, pub_y) = self.recipient.xy();
|
||||
|
||||
// Create the clear input
|
||||
let input = ClearInput {
|
||||
value: self.amount,
|
||||
token_id,
|
||||
value_blind,
|
||||
token_blind,
|
||||
signature_public: PublicKey::from_secret(mint_authority),
|
||||
};
|
||||
|
||||
// Create the anonymous output
|
||||
let coin = poseidon_hash([
|
||||
pub_x,
|
||||
pub_y,
|
||||
pallas::Base::from(self.amount),
|
||||
token_id.inner(),
|
||||
serial,
|
||||
self.spend_hook,
|
||||
self.user_data,
|
||||
coin_blind,
|
||||
]);
|
||||
|
||||
let note = MoneyNote {
|
||||
serial,
|
||||
value: self.amount,
|
||||
token_id,
|
||||
spend_hook,
|
||||
user_data,
|
||||
coin_blind,
|
||||
value_blind,
|
||||
token_blind,
|
||||
memo: vec![],
|
||||
};
|
||||
|
||||
let encrypted_note = note.encrypt(&self.recipient)?;
|
||||
|
||||
let output = Output {
|
||||
value_commit: pedersen_commitment_u64(self.amount, value_blind),
|
||||
token_commit: pedersen_commitment_base(token_id, token_blind),
|
||||
coin,
|
||||
ciphertext: encrypted_note.ciphertext,
|
||||
ephem_public: encrypted_note.ephem_public,
|
||||
};
|
||||
|
||||
Ok(MoneyMintParams { input, output })
|
||||
}
|
||||
|
||||
pub fn prove(&self, params: &MoneyMintParams, zkbin: &ZkBinary, proving_key: &ProvingKey) -> Result<Proof> {
|
||||
let prover_witnesses = vec![];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zkbin.clone());
|
||||
let proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)?;
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,11 @@
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
pallas, pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, Coin,
|
||||
ContractId, MerkleNode, MerkleTree, PublicKey, DARK_TOKEN_ID,
|
||||
pallas, pasta_prelude::*, pedersen_commitment_base, Coin, ContractId, MerkleNode,
|
||||
MerkleTree, PublicKey, DARK_TOKEN_ID,
|
||||
},
|
||||
db::{
|
||||
db_contains_key, db_get, db_init, db_lookup, db_set, set_return_data,
|
||||
SMART_CONTRACT_ZKAS_DB_NAME,
|
||||
db_contains_key, db_init, db_lookup, db_set, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME,
|
||||
},
|
||||
error::ContractResult,
|
||||
merkle::merkle_add,
|
||||
@@ -66,10 +65,22 @@ impl TryFrom<u8> for MoneyFunction {
|
||||
/// Structures and object definitions
|
||||
pub mod model;
|
||||
|
||||
// Contract functionalities
|
||||
mod mint;
|
||||
use mint::{money_mint_get_metadata, money_mint_process_instruction, money_mint_process_update};
|
||||
mod swap;
|
||||
use swap::{
|
||||
money_otcswap_get_metadata, money_otcswap_process_instruction, money_otcswap_process_update,
|
||||
};
|
||||
mod transfer;
|
||||
use transfer::{
|
||||
money_transfer_get_metadata, money_transfer_process_instruction, money_transfer_process_update,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
use model::{
|
||||
MoneyStakeParams, MoneyStakeUpdate, MoneyTransferParams, MoneyTransferUpdate,
|
||||
MoneyUnstakeParams,
|
||||
MoneyMintParams, MoneyMintUpdate, MoneyStakeParams, MoneyStakeUpdate, MoneyTransferParams,
|
||||
MoneyTransferUpdate, MoneyUnstakeParams,
|
||||
};
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
@@ -87,7 +98,6 @@ 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_TOKEN_ROOTS_TREE: &str = "token_roots";
|
||||
pub const MONEY_CONTRACT_TOKEN_FREEZE_TREE: &str = "token_freezes";
|
||||
pub const MONEY_CONTRACT_INFO_TREE: &str = "info";
|
||||
// lead coin, nullifier sled trees.
|
||||
@@ -137,8 +147,8 @@ fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let lead_mint_v1_bincode = include_bytes!("../proof/lead_mint_v1.zk.bin");
|
||||
let lead_burn_v1_bincode = include_bytes!("../proof/lead_burn_v1.zk.bin");
|
||||
|
||||
/* TODO: Do I really want to make zkas a dependency? Yeah, in the future.
|
||||
For now we take anything.
|
||||
/* For now we take anything, but the zkas db needs protection against
|
||||
arbitrary data.
|
||||
let zkbin = ZkBinary::decode(mint_bincode)?;
|
||||
let mint_namespace = zkbin.namespace.clone();
|
||||
assert_eq!(&mint_namespace, ZKAS_MINT_NS);
|
||||
@@ -166,11 +176,6 @@ fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
db_init(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
||||
}
|
||||
|
||||
// Set up a database tree to hold Merkle roots of all tokens
|
||||
if db_lookup(cid, MONEY_CONTRACT_TOKEN_ROOTS_TREE).is_err() {
|
||||
db_init(cid, MONEY_CONTRACT_TOKEN_ROOTS_TREE)?;
|
||||
}
|
||||
|
||||
// Set up a database tree to hold a set of frozen token mints
|
||||
if db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE).is_err() {
|
||||
db_init(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
|
||||
@@ -214,71 +219,26 @@ fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
/// This function is used by the VM's host to fetch the necessary metadata for
|
||||
/// verifying signatures and zk proofs.
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
assert!(call_idx < call.len() as u32);
|
||||
fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
assert!(call_idx < calls.len() as u32);
|
||||
|
||||
let self_ = &call[call_idx as usize];
|
||||
let self_ = &calls[call_idx as usize];
|
||||
|
||||
match MoneyFunction::try_from(self_.data[0])? {
|
||||
MoneyFunction::Transfer | MoneyFunction::OtcSwap => {
|
||||
let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let mut signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
for input in ¶ms.clear_inputs {
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
}
|
||||
|
||||
for input in ¶ms.inputs {
|
||||
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();
|
||||
|
||||
zk_public_values.push((
|
||||
MONEY_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,
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
}
|
||||
|
||||
for output in ¶ms.outputs {
|
||||
let value_coords = output.value_commit.to_affine().coordinates().unwrap();
|
||||
let token_coords = output.token_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_public_values.push((
|
||||
MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
|
||||
vec![
|
||||
output.coin,
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
MoneyFunction::Transfer => {
|
||||
let metadata = money_transfer_get_metadata(cid, call_idx, calls)?;
|
||||
// Using this, we pass the above data to the host.
|
||||
set_return_data(&metadata)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
MoneyFunction::OtcSwap => {
|
||||
let metadata = money_otcswap_get_metadata(cid, call_idx, calls)?;
|
||||
set_return_data(&metadata)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
MoneyFunction::Stake => {
|
||||
let params: MoneyStakeParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
@@ -376,8 +336,10 @@ fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
}
|
||||
|
||||
MoneyFunction::Mint => {
|
||||
msg!("[Mint] Entered match arm");
|
||||
unimplemented!();
|
||||
let metadata = money_mint_get_metadata(cid, call_idx, calls)?;
|
||||
// Using this, we pass the above data to the host.
|
||||
set_return_data(&metadata)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
MoneyFunction::Freeze => {
|
||||
@@ -391,222 +353,29 @@ fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
/// update if everything is successful.
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
assert!(call_idx < call.len() as u32);
|
||||
let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
|
||||
let self_ = &call[call_idx as usize];
|
||||
if call_idx >= calls.len() as u32 {
|
||||
msg!("Error: call_idx >= calls.len()");
|
||||
return Err(ContractError::Internal)
|
||||
}
|
||||
|
||||
let self_ = &calls[call_idx as usize];
|
||||
|
||||
match MoneyFunction::try_from(self_.data[0])? {
|
||||
MoneyFunction::Transfer => {
|
||||
msg!("[Transfer] Entered match arm");
|
||||
let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
assert!(params.clear_inputs.len() + params.inputs.len() > 0);
|
||||
assert!(!params.outputs.is_empty());
|
||||
|
||||
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
|
||||
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
let Some(faucet_pubkeys) = db_get(info_db, &serialize(&MONEY_CONTRACT_FAUCET_PUBKEYS))? else {
|
||||
msg!("[Transfer] Error: Missing faucet pubkeys from info db");
|
||||
return Err(ContractError::Internal);
|
||||
};
|
||||
let faucet_pubkeys: Vec<PublicKey> = deserialize(&faucet_pubkeys)?;
|
||||
|
||||
// Accumulator for the value commitments
|
||||
let mut valcom_total = pallas::Point::identity();
|
||||
|
||||
// State transition for payments
|
||||
msg!("[Transfer] Iterating over clear inputs");
|
||||
for (i, input) in params.clear_inputs.iter().enumerate() {
|
||||
let pk = input.signature_public;
|
||||
|
||||
if !faucet_pubkeys.contains(&pk) {
|
||||
msg!("[Transfer] Error: Clear input {} has invalid faucet pubkey", i);
|
||||
return Err(ContractError::Custom(20))
|
||||
}
|
||||
|
||||
valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
|
||||
}
|
||||
|
||||
let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
|
||||
|
||||
msg!("[Transfer] Iterating over anonymous inputs");
|
||||
for (i, input) in params.inputs.iter().enumerate() {
|
||||
// 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))? {
|
||||
msg!("[Transfer] Error: Merkle root not found in previous state (input {})", i);
|
||||
return Err(ContractError::Custom(21))
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist. It is the double-spend protection.
|
||||
if new_nullifiers.contains(&input.nullifier) ||
|
||||
db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
|
||||
{
|
||||
msg!("[Transfer] Error: Duplicate nullifier found in input {}", i);
|
||||
return Err(ContractError::Custom(22))
|
||||
}
|
||||
|
||||
// Check the invoked contract if spend hook is set
|
||||
if !bool::from(input.spend_hook.is_zero()) {
|
||||
let next_call_idx = call_idx + 1;
|
||||
if next_call_idx >= call.len() as u32 {
|
||||
msg!(
|
||||
"[Transfer] Error: next_call_idx = {} but len(calls) = {} in input {}",
|
||||
next_call_idx,
|
||||
call.len(),
|
||||
i
|
||||
);
|
||||
return Err(ContractError::Custom(23))
|
||||
}
|
||||
|
||||
let next = &call[next_call_idx as usize];
|
||||
if next.contract_id.inner() != input.spend_hook {
|
||||
msg!(
|
||||
"[Transfer] Error: invoking contract call does not match spend hook\
|
||||
in input {}",
|
||||
i
|
||||
);
|
||||
return Err(ContractError::Custom(24))
|
||||
}
|
||||
}
|
||||
|
||||
new_nullifiers.push(input.nullifier);
|
||||
valcom_total += input.value_commit;
|
||||
}
|
||||
|
||||
// Newly created coins for this transaction are in the outputs.
|
||||
let mut new_coins = Vec::with_capacity(params.outputs.len());
|
||||
for (i, output) in params.outputs.iter().enumerate() {
|
||||
// TODO: Should we have coins in a sled tree too to check dupes?
|
||||
if new_coins.contains(&Coin::from(output.coin)) {
|
||||
msg!("[Transfer] Error: Duplicate coin found in output {}", i);
|
||||
return Err(ContractError::Custom(25))
|
||||
}
|
||||
|
||||
// FIXME: Needs some work on types and their place within all these libraries
|
||||
new_coins.push(Coin::from(output.coin));
|
||||
valcom_total -= output.value_commit;
|
||||
}
|
||||
|
||||
// If the accumulator is not back in its initial state, there's a value mismatch.
|
||||
if valcom_total != pallas::Point::identity() {
|
||||
msg!("[Transfer] Error: Value commitments do not result in identity");
|
||||
return Err(ContractError::Custom(26))
|
||||
}
|
||||
|
||||
// Verify that the token commitments are all for the same token
|
||||
let tokcom = params.outputs[0].token_commit;
|
||||
let mut failed_tokcom = params.inputs.iter().any(|input| input.token_commit != tokcom);
|
||||
|
||||
failed_tokcom =
|
||||
failed_tokcom || params.outputs.iter().any(|output| output.token_commit != tokcom);
|
||||
|
||||
failed_tokcom = failed_tokcom ||
|
||||
params.clear_inputs.iter().any(|input| {
|
||||
pedersen_commitment_base(input.token_id.inner(), input.token_blind) != tokcom
|
||||
});
|
||||
|
||||
if failed_tokcom {
|
||||
msg!("[Transfer] Error: Token commitments do not match");
|
||||
return Err(ContractError::Custom(25))
|
||||
}
|
||||
|
||||
// Create a state update
|
||||
let update = MoneyTransferUpdate { nullifiers: new_nullifiers, coins: new_coins };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(MoneyFunction::Transfer as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
let update_data = money_transfer_process_instruction(cid, call_idx, calls)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[Transfer] State update set!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
MoneyFunction::OtcSwap => {
|
||||
msg!("[OtcSwap] Entered match arm");
|
||||
let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
// State transition for OTC swaps
|
||||
// For now we enforce 2 inputs and 2 outputs, which means the coins
|
||||
// must be available beforehand. We might want to change this and
|
||||
// allow transactions including leftover change.
|
||||
assert!(params.clear_inputs.is_empty());
|
||||
assert!(params.inputs.len() == 2);
|
||||
assert!(params.outputs.len() == 2);
|
||||
|
||||
let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
|
||||
|
||||
// inputs[0] is being swapped to outputs[1]
|
||||
// inputs[1] is being swapped to outputs[0]
|
||||
// So that's how we check the value and token commitments
|
||||
if params.inputs[0].value_commit != params.outputs[1].value_commit {
|
||||
msg!("[OtcSwap] Error: Value commitments for input 0 and output 1 do not match");
|
||||
return Err(ContractError::Custom(24))
|
||||
}
|
||||
|
||||
if params.inputs[1].value_commit != params.outputs[0].value_commit {
|
||||
msg!("[OtcSwap] Error: Value commitments for input 1 and output 0 do not match");
|
||||
return Err(ContractError::Custom(24))
|
||||
}
|
||||
|
||||
if params.inputs[0].token_commit != params.outputs[1].token_commit {
|
||||
msg!("[OtcSwap] Error: Token commitments for input 0 and output 1 do not match");
|
||||
return Err(ContractError::Custom(25))
|
||||
}
|
||||
|
||||
if params.inputs[1].token_commit != params.outputs[0].token_commit {
|
||||
msg!("[OtcSwap] Error: Token commitments for input 1 and output 0 do not match");
|
||||
return Err(ContractError::Custom(25))
|
||||
}
|
||||
|
||||
msg!("[OtcSwap] Iterating over anonymous inputs");
|
||||
for (i, input) in params.inputs.iter().enumerate() {
|
||||
// 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))? {
|
||||
msg!("[OtcSwap] Error: Merkle root not found in previous state (input {})", i);
|
||||
return Err(ContractError::Custom(21))
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist. It is the double-spend protection.
|
||||
if new_nullifiers.contains(&input.nullifier) ||
|
||||
db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
|
||||
{
|
||||
msg!("[OtcSwap] Error: Duplicate nullifier found in input {}", i);
|
||||
return Err(ContractError::Custom(22))
|
||||
}
|
||||
|
||||
new_nullifiers.push(input.nullifier);
|
||||
}
|
||||
|
||||
// Newly created coins for this transaction are in the outputs.
|
||||
let mut new_coins = Vec::with_capacity(params.outputs.len());
|
||||
for (i, output) in params.outputs.iter().enumerate() {
|
||||
// TODO: Should we have coins in a sled tree too to check dupes?
|
||||
if new_coins.contains(&Coin::from(output.coin)) {
|
||||
msg!("[OtcSwap] Error: Duplicate coin found in output {}", i);
|
||||
return Err(ContractError::Custom(23))
|
||||
}
|
||||
|
||||
// FIXME: Needs some work on types and their place within all these libraries
|
||||
new_coins.push(Coin::from(output.coin));
|
||||
}
|
||||
|
||||
// Create a state update. We also use the MoneyTransferUpdate because they're
|
||||
// essentially the same thing just with a different transition ruleset.
|
||||
let update = MoneyTransferUpdate { nullifiers: new_nullifiers, coins: new_coins };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(MoneyFunction::OtcSwap as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
let update_data = money_otcswap_process_instruction(cid, call_idx, calls)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[OtcSwap] State update set!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -766,7 +535,10 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
|
||||
MoneyFunction::Mint => {
|
||||
msg!("[Mint] Entered match arm");
|
||||
unimplemented!();
|
||||
let update_data = money_mint_process_instruction(cid, call_idx, calls)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[Mint] State update set!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
MoneyFunction::Freeze => {
|
||||
@@ -779,26 +551,15 @@ fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
|
||||
match MoneyFunction::try_from(update_data[0])? {
|
||||
MoneyFunction::Transfer | MoneyFunction::OtcSwap => {
|
||||
MoneyFunction::Transfer => {
|
||||
let update: MoneyTransferUpdate = deserialize(&update_data[1..])?;
|
||||
money_transfer_process_update(cid, update)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
|
||||
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
for nullifier in update.nullifiers {
|
||||
db_set(nullifiers_db, &serialize(&nullifier), &[])?;
|
||||
}
|
||||
|
||||
msg!("Adding coins {:?} to Merkle tree", update.coins);
|
||||
let coins: Vec<_> = update.coins.iter().map(|x| MerkleNode::from(x.inner())).collect();
|
||||
merkle_add(
|
||||
info_db,
|
||||
coin_roots_db,
|
||||
&serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE),
|
||||
&coins,
|
||||
)?;
|
||||
|
||||
MoneyFunction::OtcSwap => {
|
||||
let update: MoneyTransferUpdate = deserialize(&update_data[1..])?;
|
||||
money_otcswap_process_update(cid, update)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -826,8 +587,9 @@ fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
|
||||
}
|
||||
|
||||
MoneyFunction::Mint => {
|
||||
msg!("[Mint] Entered match arm");
|
||||
unimplemented!();
|
||||
let update: MoneyMintUpdate = deserialize(&update_data[1..])?;
|
||||
money_mint_process_update(cid, update)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
MoneyFunction::Freeze => {
|
||||
|
||||
145
src/contract/money/src/mint.rs
Normal file
145
src/contract/money/src/mint.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, poseidon_hash, Coin,
|
||||
ContractId, MerkleNode, PublicKey, TokenId,
|
||||
},
|
||||
db::{db_contains_key, db_lookup},
|
||||
error::ContractError,
|
||||
merkle_add, msg,
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
MoneyFunction, MoneyMintParams, MoneyMintUpdate, MONEY_CONTRACT_COIN_MERKLE_TREE,
|
||||
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_TOKEN_FREEZE_TREE,
|
||||
MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
|
||||
};
|
||||
|
||||
pub fn money_mint_get_metadata(
|
||||
_cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: MoneyMintParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let mut signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
signature_pubkeys.push(params.input.signature_public);
|
||||
|
||||
let value_coords = params.output.value_commit.to_affine().coordinates().unwrap();
|
||||
let token_coords = params.output.token_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let (sig_x, sig_y) = params.input.signature_public.xy();
|
||||
let token_id = poseidon_hash([sig_x, sig_y]);
|
||||
|
||||
zk_public_values.push((
|
||||
MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1.to_string(),
|
||||
vec![
|
||||
sig_x,
|
||||
sig_y,
|
||||
token_id,
|
||||
params.output.coin,
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
pub fn money_mint_process_instruction(
|
||||
cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: MoneyMintParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
//let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
|
||||
//let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
let token_freeze_db = db_lookup(cid, MONEY_CONTRACT_TOKEN_FREEZE_TREE)?;
|
||||
|
||||
// Check that the signature public key is actually the token ID
|
||||
let (mint_x, mint_y) = params.input.signature_public.xy();
|
||||
let token_id = TokenId::from(poseidon_hash([mint_x, mint_y]));
|
||||
if token_id != params.input.token_id {
|
||||
msg!("[Mint] Token ID does not derive from mint authority");
|
||||
return Err(ContractError::Custom(18))
|
||||
}
|
||||
|
||||
// Check that the mint is not frozen
|
||||
if db_contains_key(token_freeze_db, &serialize(&token_id))? {
|
||||
msg!("[Mint] Error: The mint for token {} is frozen", token_id);
|
||||
return Err(ContractError::Custom(19))
|
||||
}
|
||||
|
||||
// TODO: Check that the new coin did not exist before. We should
|
||||
// probably have a sled tree of all coins ever in order to
|
||||
// assert against duplicates.
|
||||
|
||||
// Verify that the value and token commitments match
|
||||
if pedersen_commitment_u64(params.input.value, params.input.value_blind) !=
|
||||
params.output.value_commit
|
||||
{
|
||||
msg!("[Mint] Error: Value commitments do not match");
|
||||
return Err(ContractError::Custom(10))
|
||||
}
|
||||
|
||||
if pedersen_commitment_base(params.input.token_id.inner(), params.input.token_blind) !=
|
||||
params.output.token_commit
|
||||
{
|
||||
msg!("[Mint] Error: Token commitments do not match");
|
||||
return Err(ContractError::Custom(11))
|
||||
}
|
||||
|
||||
// Create a state update. We only need the new coin.
|
||||
let update = MoneyMintUpdate { coin: Coin::from(params.output.coin) };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(MoneyFunction::Mint as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
|
||||
Ok(update_data)
|
||||
}
|
||||
|
||||
pub fn money_mint_process_update(
|
||||
cid: ContractId,
|
||||
update: MoneyMintUpdate,
|
||||
) -> Result<(), ContractError> {
|
||||
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
let coins = vec![MerkleNode::from(update.coin.inner())];
|
||||
|
||||
msg!("[Mint] Adding new coin to Merkle tree");
|
||||
merkle_add(info_db, coin_roots_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coins)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -21,6 +21,17 @@ use darkfi_sdk::crypto::{
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct MoneyMintParams {
|
||||
pub input: ClearInput,
|
||||
pub output: Output,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct MoneyMintUpdate {
|
||||
pub coin: Coin,
|
||||
}
|
||||
|
||||
/// Inputs and outputs for staking coins
|
||||
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct MoneyStakeParams {
|
||||
|
||||
150
src/contract/money/src/swap.rs
Normal file
150
src/contract/money/src/swap.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::{
|
||||
crypto::{Coin, ContractId},
|
||||
db::{db_contains_key, db_lookup},
|
||||
error::ContractError,
|
||||
msg,
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
money_transfer_get_metadata, money_transfer_process_update, MoneyFunction, MoneyTransferParams,
|
||||
MoneyTransferUpdate, MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_NULLIFIERS_TREE,
|
||||
};
|
||||
|
||||
pub fn money_otcswap_get_metadata(
|
||||
cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
Ok(money_transfer_get_metadata(cid, call_idx, calls)?)
|
||||
}
|
||||
|
||||
pub fn money_otcswap_process_instruction(
|
||||
cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
// State transition for OTC atomic swaps.
|
||||
// We enforce 2 inputs and 2 outputs so every atomic swap looks the same.
|
||||
if !params.clear_inputs.is_empty() {
|
||||
msg!("[OtcSwap] Error: Clear inputs are not empty");
|
||||
return Err(ContractError::Custom(12))
|
||||
}
|
||||
|
||||
if params.inputs.len() != 2 {
|
||||
msg!("[OtcSwap] Error: Expected 2 inputs");
|
||||
return Err(ContractError::Custom(13))
|
||||
}
|
||||
|
||||
if params.outputs.len() != 2 {
|
||||
msg!("[OtcSwap] Error: Expected 2 outputs");
|
||||
return Err(ContractError::Custom(14))
|
||||
}
|
||||
|
||||
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
let mut new_nullifiers = Vec::with_capacity(2);
|
||||
|
||||
// inputs[0] is being swapped to outputs[1]
|
||||
// inputs[1] is being swapped to outputs[0]
|
||||
// So that's how we check the value and token commitments
|
||||
if params.inputs[0].value_commit != params.outputs[1].value_commit {
|
||||
msg!("[OtcSwap] Error: Value commitments for input 0 and output 1 do not match");
|
||||
return Err(ContractError::Custom(10))
|
||||
}
|
||||
|
||||
if params.inputs[1].value_commit != params.outputs[0].value_commit {
|
||||
msg!("[OtcSwap] Error: Value commitments for input 1 and output 0 do not match");
|
||||
return Err(ContractError::Custom(10))
|
||||
}
|
||||
|
||||
if params.inputs[0].token_commit != params.outputs[1].token_commit {
|
||||
msg!("[OtcSwap] Error: Token commitments for input 0 and output 1 do not match");
|
||||
return Err(ContractError::Custom(11))
|
||||
}
|
||||
|
||||
if params.inputs[1].token_commit != params.outputs[0].token_commit {
|
||||
msg!("[OtcSwap] Error: Token commitments for input 1 and output 0 do not match");
|
||||
return Err(ContractError::Custom(11))
|
||||
}
|
||||
|
||||
msg!("[OtcSwap] Iternating over anonymous inputs");
|
||||
for (i, input) in params.inputs.iter().enumerate() {
|
||||
// For now, make sure that the inputs' spend hooks are zero.
|
||||
// This should however be allowed to some extent.
|
||||
if input.spend_hook != pallas::Base::zero() {
|
||||
msg!("[OtcSwap] Error: Unable to swap coins with spend_hook != 0 (input {})", i);
|
||||
return Err(ContractError::Custom(17))
|
||||
}
|
||||
|
||||
// The Merkle root is used to know whether this coin existed
|
||||
// in a previous state.
|
||||
if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
|
||||
msg!("[OtcSwap] Error: Merkle root not found in previous state (input {})", i);
|
||||
return Err(ContractError::Custom(5))
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist. It is the double-spend protection.
|
||||
if new_nullifiers.contains(&input.nullifier) ||
|
||||
db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
|
||||
{
|
||||
msg!("[OtcSwap] Error: Duplicate nullifier found in input {}", i);
|
||||
return Err(ContractError::Custom(6))
|
||||
}
|
||||
|
||||
new_nullifiers.push(input.nullifier);
|
||||
}
|
||||
|
||||
// Newly created coins for this transaction are in the outputs.
|
||||
let mut new_coins = Vec::with_capacity(2);
|
||||
for (i, output) in params.outputs.iter().enumerate() {
|
||||
// TODO: Coins should exist in a sled tree in order to check dupes.
|
||||
if new_coins.contains(&Coin::from(output.coin)) {
|
||||
msg!("[OtcSwap] Error: Duplicate coin found in output {}", i);
|
||||
return Err(ContractError::Custom(9))
|
||||
}
|
||||
|
||||
new_coins.push(Coin::from(output.coin));
|
||||
}
|
||||
|
||||
// Create a state update. We also use the `MoneyTransferUpdate` because
|
||||
// they're essentially the same thing, just with a different transition
|
||||
// rule set.
|
||||
let update = MoneyTransferUpdate { nullifiers: new_nullifiers, coins: new_coins };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(MoneyFunction::OtcSwap as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
|
||||
Ok(update_data)
|
||||
}
|
||||
|
||||
pub fn money_otcswap_process_update(
|
||||
cid: ContractId,
|
||||
update: MoneyTransferUpdate,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(money_transfer_process_update(cid, update)?)
|
||||
}
|
||||
251
src/contract/money/src/transfer.rs
Normal file
251
src/contract/money/src/transfer.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2023 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
pasta_prelude::*, pedersen_commitment_base, pedersen_commitment_u64, Coin, ContractId,
|
||||
MerkleNode, PublicKey, DARK_TOKEN_ID,
|
||||
},
|
||||
db::{db_contains_key, db_get, db_lookup, db_set},
|
||||
error::ContractError,
|
||||
merkle_add, msg,
|
||||
pasta::pallas,
|
||||
ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
use crate::{
|
||||
MoneyFunction, MoneyTransferParams, MoneyTransferUpdate, MONEY_CONTRACT_COIN_MERKLE_TREE,
|
||||
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_FAUCET_PUBKEYS, MONEY_CONTRACT_INFO_TREE,
|
||||
MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
|
||||
};
|
||||
|
||||
pub fn money_transfer_get_metadata(
|
||||
_cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let mut signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
for input in ¶ms.clear_inputs {
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
}
|
||||
|
||||
for input in ¶ms.inputs {
|
||||
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();
|
||||
|
||||
zk_public_values.push((
|
||||
MONEY_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,
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
}
|
||||
|
||||
for output in ¶ms.outputs {
|
||||
let value_coords = output.value_commit.to_affine().coordinates().unwrap();
|
||||
let token_coords = output.token_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_public_values.push((
|
||||
MONEY_CONTRACT_ZKAS_MINT_NS_V1.to_string(),
|
||||
vec![
|
||||
output.coin,
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
pub fn money_transfer_process_instruction(
|
||||
cid: ContractId,
|
||||
call_idx: u32,
|
||||
calls: Vec<ContractCall>,
|
||||
) -> Result<Vec<u8>, ContractError> {
|
||||
let self_ = &calls[call_idx as usize];
|
||||
let params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
if params.clear_inputs.len() + params.inputs.len() < 1 {
|
||||
msg!("[Transfer] Error: No inputs in the call");
|
||||
return Err(ContractError::Custom(1))
|
||||
}
|
||||
|
||||
if params.outputs.is_empty() {
|
||||
msg!("[Transfer] Error: No outputs in the call");
|
||||
return Err(ContractError::Custom(2))
|
||||
}
|
||||
|
||||
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
|
||||
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
let Some(faucet_pubkeys) = db_get(info_db, &serialize(&MONEY_CONTRACT_FAUCET_PUBKEYS))? else {
|
||||
msg!("[Transfer] Error: Missing faucet pubkeys from info db");
|
||||
return Err(ContractError::Internal)
|
||||
};
|
||||
let faucet_pubkeys: Vec<PublicKey> = deserialize(&faucet_pubkeys)?;
|
||||
|
||||
// Accumulator for the value commitments
|
||||
let mut valcom_total = pallas::Point::identity();
|
||||
|
||||
// State transition for payments
|
||||
msg!("[Transfer] Iterating over clear inputs");
|
||||
for (i, input) in params.clear_inputs.iter().enumerate() {
|
||||
if input.token_id != *DARK_TOKEN_ID {
|
||||
msg!("[Transfer] Error: Clear input {} used non-native token", i);
|
||||
return Err(ContractError::Custom(3))
|
||||
}
|
||||
|
||||
if !faucet_pubkeys.contains(&input.signature_public) {
|
||||
msg!("[Transfer] Error: Clear input {} used unauthorised pubkey", i);
|
||||
return Err(ContractError::Custom(4))
|
||||
}
|
||||
|
||||
valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
|
||||
}
|
||||
|
||||
let mut new_nullifiers = Vec::with_capacity(params.inputs.len());
|
||||
msg!("[Transfer] Iterating over anonymous inputs");
|
||||
for (i, input) in params.inputs.iter().enumerate() {
|
||||
// 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))? {
|
||||
msg!("[Transfer] Error: Merkle root not found in previous state (input {})", i);
|
||||
return Err(ContractError::Custom(5))
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist. It is the double-spend protection.
|
||||
if new_nullifiers.contains(&input.nullifier) ||
|
||||
db_contains_key(nullifiers_db, &serialize(&input.nullifier))?
|
||||
{
|
||||
msg!("[Transfer] Error: Duplicate nullifier found in input {}", i);
|
||||
return Err(ContractError::Custom(6))
|
||||
}
|
||||
|
||||
// Check the invoked contract if spend hook is set
|
||||
if input.spend_hook != pallas::Base::zero() {
|
||||
let next_call_idx = call_idx + 1;
|
||||
if next_call_idx >= calls.len() as u32 {
|
||||
msg!(
|
||||
"[Transfer] Error: next_call_idx={} but len(calls)={} (input {})",
|
||||
next_call_idx,
|
||||
calls.len(),
|
||||
i
|
||||
);
|
||||
return Err(ContractError::Custom(7))
|
||||
}
|
||||
|
||||
let next = &calls[next_call_idx as usize];
|
||||
if next.contract_id.inner() != input.spend_hook {
|
||||
msg!("[Transfer] Error: Invoking contract call does not match spend hook in input {}", i);
|
||||
return Err(ContractError::Custom(8))
|
||||
}
|
||||
}
|
||||
|
||||
new_nullifiers.push(input.nullifier);
|
||||
valcom_total += input.value_commit;
|
||||
}
|
||||
|
||||
// Newly created coins for this transaction are in the outputs.
|
||||
let mut new_coins = Vec::with_capacity(params.outputs.len());
|
||||
for (i, output) in params.outputs.iter().enumerate() {
|
||||
// TODO: Coins should exist in a sled tree in order to check dupes.
|
||||
if new_coins.contains(&Coin::from(output.coin)) {
|
||||
msg!("[Transfer] Error: Duplicate coin found in output {}", i);
|
||||
return Err(ContractError::Custom(9))
|
||||
}
|
||||
|
||||
// FIXME: Needs some work on types and their place within all these libraries
|
||||
new_coins.push(Coin::from(output.coin));
|
||||
valcom_total -= output.value_commit;
|
||||
}
|
||||
|
||||
// If the accumulator is not back in its initial state, there's a value mismatch.
|
||||
if valcom_total != pallas::Point::identity() {
|
||||
msg!("[Transfer] Error: Value commitments do not result in identity");
|
||||
return Err(ContractError::Custom(10))
|
||||
}
|
||||
|
||||
// Verify that the token commitments are all for the same token
|
||||
let tokcom = params.outputs[0].token_commit;
|
||||
let mut failed_tokcom = params.inputs.iter().any(|input| input.token_commit != tokcom);
|
||||
failed_tokcom =
|
||||
failed_tokcom || params.outputs.iter().any(|output| output.token_commit != tokcom);
|
||||
failed_tokcom = failed_tokcom ||
|
||||
params.clear_inputs.iter().any(|input| {
|
||||
pedersen_commitment_base(input.token_id.inner(), input.token_blind) != tokcom
|
||||
});
|
||||
|
||||
if failed_tokcom {
|
||||
msg!("[Transfer] Error: Token commitments do not match");
|
||||
return Err(ContractError::Custom(11))
|
||||
}
|
||||
|
||||
// Create a state update
|
||||
let update = MoneyTransferUpdate { nullifiers: new_nullifiers, coins: new_coins };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(MoneyFunction::Transfer as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
|
||||
Ok(update_data)
|
||||
}
|
||||
|
||||
pub fn money_transfer_process_update(
|
||||
cid: ContractId,
|
||||
update: MoneyTransferUpdate,
|
||||
) -> Result<(), ContractError> {
|
||||
let info_db = db_lookup(cid, MONEY_CONTRACT_INFO_TREE)?;
|
||||
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
|
||||
|
||||
msg!("[Transfer] Adding new nullifiers to the set");
|
||||
for nullifier in update.nullifiers {
|
||||
db_set(nullifiers_db, &serialize(&nullifier), &[])?;
|
||||
}
|
||||
|
||||
let coins: Vec<_> = update.coins.iter().map(|x| MerkleNode::from(x.inner())).collect();
|
||||
|
||||
msg!("[Transfer] Adding new coins to Merkle tree");
|
||||
merkle_add(info_db, coin_roots_db, &serialize(&MONEY_CONTRACT_COIN_MERKLE_TREE), &coins)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user