mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
Remove example/dao2
This commit is contained in:
5
example/dao2/.gitignore
vendored
5
example/dao2/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
/dao
|
||||
*.wasm
|
||||
*.zk.bin
|
||||
target/
|
||||
Cargo.lock
|
||||
@@ -1,54 +0,0 @@
|
||||
[package]
|
||||
name = "dao"
|
||||
version = "0.4.1"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"contract/money",
|
||||
"contract/dao",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
dao-contract = {path = "contract/dao"}
|
||||
money-contract = {path = "contract/money"}
|
||||
darkfi-sdk = { path = "../../src/sdk" }
|
||||
darkfi-serial = { path = "../../src/serial" }
|
||||
darkfi = { path = "../../", features = ["wasm-runtime"] }
|
||||
sled = "0.34.7"
|
||||
|
||||
# Async
|
||||
smol = "1.3.0"
|
||||
futures = "0.3.28"
|
||||
async-std = {version = "1.12.0", features = ["attributes"]}
|
||||
async-trait = "0.1.68"
|
||||
async-channel = "1.8.0"
|
||||
async-executor = "1.5.1"
|
||||
easy-parallel = "3.3.0"
|
||||
|
||||
# Misc
|
||||
log = "0.4.17"
|
||||
num_cpus = "1.15.0"
|
||||
simplelog = "0.12.1"
|
||||
thiserror = "1.0.40"
|
||||
|
||||
# Crypto
|
||||
incrementalmerkletree = "0.3.1"
|
||||
pasta_curves = "0.5.1"
|
||||
halo2_gadgets = "0.3.0"
|
||||
halo2_proofs = "0.3.0"
|
||||
rand = "0.8.5"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
group = "0.13.0"
|
||||
|
||||
# Encoding and parsing
|
||||
serde_json = "1.0.96"
|
||||
bs58 = "0.4.0"
|
||||
fxhash = "0.2.1"
|
||||
|
||||
# Utilities
|
||||
lazy_static = "1.4.0"
|
||||
url = "2.3.1"
|
||||
env_logger = "0.10.0"
|
||||
@@ -1,38 +0,0 @@
|
||||
.POSIX:
|
||||
|
||||
SRC = $(shell find src -type f)
|
||||
WASM_SRC = $(shell find contract -type f)
|
||||
|
||||
# Cargo binary
|
||||
CARGO = cargo
|
||||
|
||||
# Contract WASM binaries
|
||||
WASM_BIN = \
|
||||
dao_contract.wasm \
|
||||
money_contract.wasm
|
||||
|
||||
# Host binaries
|
||||
BIN = dao
|
||||
|
||||
all: $(WASM_BIN) $(BIN)
|
||||
@./dao
|
||||
|
||||
dao: $(WASM_SRC) $(SRC)
|
||||
$(CARGO) build --release --bin $@
|
||||
cp -f target/release/$@ $@
|
||||
|
||||
dao_contract.wasm: $(WASM_SRC)
|
||||
$(CARGO) build --release --package dao-contract --target wasm32-unknown-unknown
|
||||
cp -f target/wasm32-unknown-unknown/release/$@ $@
|
||||
|
||||
money_contract.wasm: $(WASM_SRC)
|
||||
$(CARGO) build --release --package money-contract --target wasm32-unknown-unknown
|
||||
cp -f target/wasm32-unknown-unknown/release/$@ $@
|
||||
|
||||
test: all
|
||||
$(CARGO) test --release -- --nocapture
|
||||
|
||||
clean:
|
||||
rm -f $(BIN) $(WASM_BIN)
|
||||
|
||||
.PHONY: all test clean
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "dao-contract"
|
||||
version = "0.4.1"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
darkfi-sdk = { path = "../../../../src/sdk" }
|
||||
darkfi-serial = { path = "../../../../src/serial", features = ["crypto"] }
|
||||
|
||||
# We need to disable random using "custom" which makes the crate a noop
|
||||
# so the wasm32-unknown-unknown target is enabled.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.8", features = ["custom"] }
|
||||
@@ -1,153 +0,0 @@
|
||||
/* 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::{ContractId, MerkleNode, MerkleTree},
|
||||
db::{db_init, db_lookup, db_set},
|
||||
define_contract,
|
||||
error::ContractResult,
|
||||
merkle::merkle_add,
|
||||
msg,
|
||||
pasta::pallas,
|
||||
tx::ContractCall,
|
||||
util::set_return_data,
|
||||
};
|
||||
use darkfi_serial::{
|
||||
deserialize, serialize, Encodable, SerialDecodable, SerialEncodable, WriteExt,
|
||||
};
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoBulla(pub pallas::Base);
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum DaoFunction {
|
||||
Foo = 0x00,
|
||||
Mint = 0x01,
|
||||
}
|
||||
|
||||
impl From<u8> for DaoFunction {
|
||||
fn from(b: u8) -> Self {
|
||||
match b {
|
||||
0x00 => Self::Foo,
|
||||
0x01 => Self::Mint,
|
||||
_ => panic!("Invalid function ID: {:#04x?}", b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoMintParams {
|
||||
pub dao_bulla: DaoBulla,
|
||||
}
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoMintUpdate {
|
||||
pub dao_bulla: DaoBulla,
|
||||
}
|
||||
|
||||
define_contract!(
|
||||
init: init_contract,
|
||||
exec: process_instruction,
|
||||
apply: process_update,
|
||||
metadata: get_metadata
|
||||
);
|
||||
|
||||
fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
|
||||
let info_db = db_init(cid, "info")?;
|
||||
let _ = db_init(cid, "dao_roots")?;
|
||||
|
||||
let dao_tree = MerkleTree::new(100);
|
||||
let mut dao_tree_data = Vec::new();
|
||||
dao_tree_data.write_u32(0)?;
|
||||
dao_tree.encode(&mut dao_tree_data)?;
|
||||
db_set(info_db, &serialize(&"dao_tree".to_string()), &dao_tree_data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
|
||||
assert!(call_idx < call.len() as u32);
|
||||
let self_ = &call[call_idx as usize];
|
||||
|
||||
match DaoFunction::from(self_.data[0]) {
|
||||
DaoFunction::Mint => {
|
||||
let data = &self_.data[1..];
|
||||
let params: DaoMintParams = deserialize(data)?;
|
||||
|
||||
let zk_public_values: Vec<(String, Vec<pallas::Base>)> =
|
||||
vec![("dao-mint".to_string(), vec![params.dao_bulla.0])];
|
||||
let signature_public_keys: Vec<pallas::Point> = Vec::new();
|
||||
|
||||
let mut metadata = Vec::new();
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_public_keys.encode(&mut metadata)?;
|
||||
set_return_data(&metadata)?;
|
||||
}
|
||||
DaoFunction::Foo => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
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 self_ = &call[call_idx as usize];
|
||||
|
||||
match DaoFunction::from(self_.data[0]) {
|
||||
DaoFunction::Mint => {
|
||||
let data = &self_.data[1..];
|
||||
let params: DaoMintParams = deserialize(data)?;
|
||||
|
||||
// No checks in Mint. Just return the update.
|
||||
|
||||
let update = DaoMintUpdate { dao_bulla: params.dao_bulla };
|
||||
|
||||
let mut update_data = Vec::new();
|
||||
update_data.write_u8(DaoFunction::Mint as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("update is set!");
|
||||
}
|
||||
DaoFunction::Foo => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
|
||||
match DaoFunction::from(update_data[0]) {
|
||||
DaoFunction::Mint => {
|
||||
let data = &update_data[1..];
|
||||
let update: DaoMintUpdate = deserialize(data)?;
|
||||
|
||||
let db_info = db_lookup(cid, "info")?;
|
||||
let db_roots = db_lookup(cid, "dao_roots")?;
|
||||
let node = MerkleNode::new(update.dao_bulla.0);
|
||||
merkle_add(db_info, db_roots, &serialize(&"dao_tree".to_string()), &node)?;
|
||||
}
|
||||
DaoFunction::Foo => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "money-contract"
|
||||
version = "0.4.1"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
darkfi-sdk = { path = "../../../../src/sdk" }
|
||||
darkfi-serial = { path = "../../../../src/serial", features = ["crypto"] }
|
||||
|
||||
# We need to disable random using "custom" which makes the crate a noop
|
||||
# so the wasm32-unknown-unknown target is enabled.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.8", features = ["custom"] }
|
||||
@@ -1,238 +0,0 @@
|
||||
/* 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::{ContractId, MerkleNode, MerkleTree, PublicKey},
|
||||
db::{db_init, db_lookup, db_set},
|
||||
define_contract,
|
||||
error::ContractResult,
|
||||
merkle::merkle_add,
|
||||
msg,
|
||||
pasta::{arithmetic::CurveAffine, group::Curve, pallas},
|
||||
tx::ContractCall,
|
||||
util::set_return_data,
|
||||
};
|
||||
use darkfi_serial::{
|
||||
deserialize, serialize, Encodable, SerialDecodable, SerialEncodable, WriteExt,
|
||||
};
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum MoneyFunction {
|
||||
Transfer = 0x00,
|
||||
}
|
||||
|
||||
impl From<u8> for MoneyFunction {
|
||||
fn from(b: u8) -> Self {
|
||||
match b {
|
||||
0x00 => Self::Transfer,
|
||||
_ => panic!("Invalid function ID: {:#04x?}", b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct MoneyTransferParams {
|
||||
/// Clear inputs
|
||||
pub clear_inputs: Vec<ClearInput>,
|
||||
/// Anonymous inputs
|
||||
pub inputs: Vec<Input>,
|
||||
/// Anonymous outputs
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct MoneyTransferUpdate {
|
||||
/// Nullifiers
|
||||
pub nullifiers: Vec<pallas::Base>,
|
||||
/// Coins
|
||||
pub coins: Vec<pallas::Base>,
|
||||
}
|
||||
|
||||
/// A transaction's clear input
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct ClearInput {
|
||||
/// Input's value (amount)
|
||||
pub value: u64,
|
||||
/// Input's token ID
|
||||
pub token_id: pallas::Base,
|
||||
/// Blinding factor for `value`
|
||||
pub value_blind: pallas::Scalar,
|
||||
/// Blinding factor for `token_id`
|
||||
pub token_blind: pallas::Scalar,
|
||||
/// Public key for the signature
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous input
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
// Public inputs for the zero-knowledge proof
|
||||
pub value_commit: pallas::Point,
|
||||
pub token_commit: pallas::Point,
|
||||
pub nullifier: pallas::Base,
|
||||
pub merkle_root: pallas::Base,
|
||||
pub spend_hook: pallas::Base,
|
||||
pub user_data_enc: pallas::Base,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous output
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Output {
|
||||
// Public inputs for the zero-knowledge proof
|
||||
pub value_commit: pallas::Point,
|
||||
pub token_commit: pallas::Point,
|
||||
pub coin: pallas::Base,
|
||||
/// The encrypted note ciphertext
|
||||
pub ciphertext: Vec<u8>,
|
||||
pub ephem_public: PublicKey,
|
||||
}
|
||||
|
||||
define_contract!(
|
||||
init: init_contract,
|
||||
exec: process_instruction,
|
||||
apply: process_update,
|
||||
metadata: get_metadata
|
||||
);
|
||||
|
||||
fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
|
||||
let info_db = db_init(cid, "info")?;
|
||||
let _ = db_init(cid, "coin_roots")?;
|
||||
|
||||
let coin_tree = MerkleTree::new(100);
|
||||
let mut coin_tree_data = Vec::new();
|
||||
coin_tree_data.write_u32(0)?;
|
||||
coin_tree.encode(&mut coin_tree_data)?;
|
||||
db_set(info_db, &serialize(&"coin_tree".to_string()), &coin_tree_data)?;
|
||||
|
||||
let _ = db_init(cid, "nulls")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn get_metadata(_cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
|
||||
assert!(call_idx < call.len() as u32);
|
||||
let self_ = &call[call_idx as usize];
|
||||
|
||||
match MoneyFunction::from(self_.data[0]) {
|
||||
MoneyFunction::Transfer => {
|
||||
let data = &self_.data[1..];
|
||||
let params: MoneyTransferParams = deserialize(data)?;
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = Vec::new();
|
||||
let mut signature_public_keys: Vec<pallas::Point> = Vec::new();
|
||||
|
||||
for input in ¶ms.clear_inputs {
|
||||
signature_public_keys.push(input.signature_public.inner());
|
||||
}
|
||||
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-transfer-burn".to_string(),
|
||||
vec![
|
||||
input.nullifier,
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
input.merkle_root,
|
||||
input.user_data_enc,
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
|
||||
signature_public_keys.push(input.signature_public.inner());
|
||||
}
|
||||
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-transfer-mint".to_string(),
|
||||
vec![
|
||||
output.coin,
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let mut metadata = Vec::new();
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_public_keys.encode(&mut metadata)?;
|
||||
set_return_data(&metadata)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
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 self_ = &call[call_idx as usize];
|
||||
|
||||
match MoneyFunction::from(self_.data[0]) {
|
||||
MoneyFunction::Transfer => {
|
||||
let data = &self_.data[1..];
|
||||
let params: MoneyTransferParams = deserialize(data)?;
|
||||
|
||||
// TODO: implement state_transition() checks here
|
||||
|
||||
let update = MoneyTransferUpdate {
|
||||
nullifiers: params.inputs.iter().map(|input| input.nullifier).collect(),
|
||||
coins: params.outputs.iter().map(|output| output.coin).collect(),
|
||||
};
|
||||
|
||||
let mut update_data = Vec::new();
|
||||
update_data.write_u8(MoneyFunction::Transfer as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("update is set!");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
|
||||
match MoneyFunction::from(update_data[0]) {
|
||||
MoneyFunction::Transfer => {
|
||||
let data = &update_data[1..];
|
||||
let update: MoneyTransferUpdate = deserialize(data)?;
|
||||
|
||||
let db_info = db_lookup(cid, "info")?;
|
||||
let db_nulls = db_lookup(cid, "nulls")?;
|
||||
for nullifier in update.nullifiers {
|
||||
db_set(db_nulls, &serialize(&nullifier), &[])?;
|
||||
}
|
||||
let db_roots = db_lookup(cid, "coin_roots")?;
|
||||
for coin in update.coins {
|
||||
let node = MerkleNode::new(coin);
|
||||
// TODO: merkle_add() should take a list of coins and batch add them
|
||||
// for efficiency
|
||||
merkle_add(db_info, db_roots, &serialize(&"coin_tree".to_string()), &node)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
constant "DaoExec" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
}
|
||||
|
||||
contract "DaoExec" {
|
||||
# proposal params
|
||||
Base proposal_dest_x,
|
||||
Base proposal_dest_y,
|
||||
Base proposal_amount,
|
||||
Base proposal_serial,
|
||||
Base proposal_token_id,
|
||||
Base proposal_blind,
|
||||
|
||||
# DAO params
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gov_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
|
||||
# votes
|
||||
Base yes_votes_value,
|
||||
Base all_votes_value,
|
||||
Scalar yes_votes_blind,
|
||||
Scalar all_votes_blind,
|
||||
|
||||
# outputs + inputs
|
||||
Base user_serial,
|
||||
Base user_coin_blind,
|
||||
Base dao_serial,
|
||||
Base dao_coin_blind,
|
||||
Base input_value,
|
||||
Scalar input_value_blind,
|
||||
|
||||
# misc
|
||||
Base dao_spend_hook,
|
||||
Base user_spend_hook,
|
||||
Base user_data,
|
||||
}
|
||||
|
||||
circuit "DaoExec" {
|
||||
dao_bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
# Proposal bulla is valid means DAO bulla is also valid
|
||||
# because of dao-propose-main.zk, already checks that when
|
||||
# we first create the proposal. So it is redundant here.
|
||||
|
||||
proposal_bulla = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_serial,
|
||||
proposal_token_id,
|
||||
dao_bulla,
|
||||
proposal_blind,
|
||||
# @tmp-workaround
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(proposal_bulla);
|
||||
|
||||
coin_0 = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_token_id,
|
||||
proposal_serial,
|
||||
user_spend_hook,
|
||||
user_data,
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(coin_0);
|
||||
|
||||
change = base_sub(input_value, proposal_amount);
|
||||
|
||||
coin_1 = poseidon_hash(
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
change,
|
||||
proposal_token_id,
|
||||
dao_serial,
|
||||
dao_spend_hook,
|
||||
dao_bulla,
|
||||
dao_coin_blind,
|
||||
);
|
||||
constrain_instance(coin_1);
|
||||
|
||||
# Create pedersen commits for win_votes, and total_votes
|
||||
# and make public
|
||||
yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE);
|
||||
yes_votes_blind_c = ec_mul(yes_votes_blind, VALUE_COMMIT_RANDOM);
|
||||
yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c);
|
||||
|
||||
# get curve points and constrain
|
||||
yes_votes_commit_x = ec_get_x(yes_votes_commit);
|
||||
yes_votes_commit_y = ec_get_y(yes_votes_commit);
|
||||
constrain_instance(yes_votes_commit_x);
|
||||
constrain_instance(yes_votes_commit_y);
|
||||
|
||||
all_votes_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE);
|
||||
all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM);
|
||||
all_votes_commit = ec_add(all_votes_c, all_votes_blind_c);
|
||||
|
||||
# get curve points and constrain
|
||||
all_votes_commit_x = ec_get_x(all_votes_commit);
|
||||
all_votes_commit_y = ec_get_y(all_votes_commit);
|
||||
constrain_instance(all_votes_commit_x);
|
||||
constrain_instance(all_votes_commit_y);
|
||||
|
||||
# Create pedersen commit for input_value and make public
|
||||
|
||||
input_value_v = ec_mul_short(input_value, VALUE_COMMIT_VALUE);
|
||||
input_value_r = ec_mul(input_value_blind, VALUE_COMMIT_RANDOM);
|
||||
input_value_commit = ec_add(input_value_v, input_value_r);
|
||||
|
||||
# get curve points and constrain
|
||||
input_value_x = ec_get_x(input_value_commit);
|
||||
input_value_y = ec_get_y(input_value_commit);
|
||||
constrain_instance(input_value_x);
|
||||
constrain_instance(input_value_y);
|
||||
|
||||
constrain_instance(dao_spend_hook);
|
||||
constrain_instance(user_spend_hook);
|
||||
constrain_instance(user_data);
|
||||
|
||||
# Check that dao_quorum is less than or equal to all_votes_value
|
||||
one = witness_base(1);
|
||||
all_votes_value_1 = base_add(all_votes_value, one);
|
||||
less_than(dao_quorum, all_votes_value_1);
|
||||
|
||||
# approval_ratio_quot / approval_ratio_base <= yes_votes / all_votes
|
||||
#
|
||||
# The above is also equivalent to this:
|
||||
#
|
||||
# all_votes * approval_ratio_quot <= yes_votes * approval_ratio_base
|
||||
|
||||
rhs = base_mul(all_votes_value, dao_approval_ratio_quot);
|
||||
lhs = base_mul(yes_votes_value, dao_approval_ratio_base);
|
||||
|
||||
lhs_1 = base_add(lhs, one);
|
||||
less_than(rhs, lhs_1);
|
||||
|
||||
####
|
||||
|
||||
# Create coin 0
|
||||
# Create coin 1
|
||||
# Check values of coin 0 + coin 1 == input value
|
||||
# Check value of coin 0 == proposal_amount
|
||||
# Check public key matches too
|
||||
# Create the input value commit
|
||||
# Create the value commits
|
||||
|
||||
# NOTE: there is a vulnerability here where someone can create the exec
|
||||
# transaction with a bad note so it cannot be decrypted by the receiver
|
||||
# TODO: research verifiable encryption inside ZK
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
constant "DaoMint" {
|
||||
}
|
||||
|
||||
contract "DaoMint" {
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gdrk_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
}
|
||||
|
||||
circuit "DaoMint" {
|
||||
# This circuit is not that interesting.
|
||||
# It just states the bulla is a hash of 8 values.
|
||||
|
||||
# BullaMint subroutine
|
||||
bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gdrk_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
constrain_instance(bulla);
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
constant "DaoProposeInput" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
EcFixedPointBase NULLIFIER_K,
|
||||
}
|
||||
|
||||
contract "DaoProposeInput" {
|
||||
Base secret,
|
||||
Base serial,
|
||||
Base spend_hook,
|
||||
Base user_data,
|
||||
Base value,
|
||||
Base token,
|
||||
Base coin_blind,
|
||||
Scalar value_blind,
|
||||
Base token_blind,
|
||||
Uint32 leaf_pos,
|
||||
MerklePath path,
|
||||
Base signature_secret,
|
||||
}
|
||||
|
||||
circuit "DaoProposeInput" {
|
||||
# Poseidon hash of the nullifier
|
||||
#nullifier = poseidon_hash(secret, serial);
|
||||
#constrain_instance(nullifier);
|
||||
|
||||
# Pedersen commitment for coin's value
|
||||
vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
|
||||
vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
|
||||
value_commit = ec_add(vcv, vcr);
|
||||
# Since value_commit is a curve point, we fetch its coordinates
|
||||
# and constrain them:
|
||||
value_commit_x = ec_get_x(value_commit);
|
||||
value_commit_y = ec_get_y(value_commit);
|
||||
constrain_instance(value_commit_x);
|
||||
constrain_instance(value_commit_y);
|
||||
|
||||
# Commitment for coin's token ID
|
||||
token_commit = poseidon_hash(token, token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
# Coin hash
|
||||
pub = ec_mul_base(secret, NULLIFIER_K);
|
||||
pub_x = ec_get_x(pub);
|
||||
pub_y = ec_get_y(pub);
|
||||
C = poseidon_hash(pub_x, pub_y, value, token, serial, spend_hook, user_data, coin_blind);
|
||||
|
||||
# Merkle root
|
||||
root = merkle_root(leaf_pos, path, C);
|
||||
constrain_instance(root);
|
||||
|
||||
# Finally, we derive a public key for the signature and
|
||||
# constrain its coordinates:
|
||||
signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
|
||||
signature_x = ec_get_x(signature_public);
|
||||
signature_y = ec_get_y(signature_public);
|
||||
constrain_instance(signature_x);
|
||||
constrain_instance(signature_y);
|
||||
|
||||
# At this point we've enforced all of our public inputs.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
constant "DaoProposeMain" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
}
|
||||
|
||||
contract "DaoProposeMain" {
|
||||
# Proposers total number of gov tokens
|
||||
Base total_funds,
|
||||
Scalar total_funds_blind,
|
||||
|
||||
# Check the inputs and this proof are for the same token
|
||||
Base gov_token_blind,
|
||||
|
||||
# proposal params
|
||||
Base proposal_dest_x,
|
||||
Base proposal_dest_y,
|
||||
Base proposal_amount,
|
||||
Base proposal_serial,
|
||||
Base proposal_token_id,
|
||||
Base proposal_blind,
|
||||
|
||||
# DAO params
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gov_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
|
||||
Uint32 dao_leaf_pos,
|
||||
MerklePath dao_path,
|
||||
}
|
||||
|
||||
circuit "DaoProposeMain" {
|
||||
token_commit = poseidon_hash(gov_token_id, gov_token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
dao_bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
dao_root = merkle_root(dao_leaf_pos, dao_path, dao_bulla);
|
||||
constrain_instance(dao_root);
|
||||
# Proves this DAO is valid
|
||||
|
||||
proposal_bulla = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_serial,
|
||||
proposal_token_id,
|
||||
dao_bulla,
|
||||
proposal_blind,
|
||||
# @tmp-workaround
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(proposal_bulla);
|
||||
|
||||
# Rangeproof check for proposal amount
|
||||
zero = witness_base(0);
|
||||
less_than(zero, proposal_amount);
|
||||
|
||||
# This is the main check
|
||||
# We check that dao_proposer_limit <= total_funds
|
||||
one = witness_base(1);
|
||||
total_funds_1 = base_add(total_funds, one);
|
||||
less_than(dao_proposer_limit, total_funds_1);
|
||||
|
||||
# Pedersen commitment for coin's value
|
||||
vcv = ec_mul_short(total_funds, VALUE_COMMIT_VALUE);
|
||||
vcr = ec_mul(total_funds_blind, VALUE_COMMIT_RANDOM);
|
||||
total_funds_commit = ec_add(vcv, vcr);
|
||||
# Since total_funds_commit is a curve point, we fetch its coordinates
|
||||
# and constrain them:
|
||||
total_funds_commit_x = ec_get_x(total_funds_commit);
|
||||
total_funds_commit_y = ec_get_y(total_funds_commit);
|
||||
constrain_instance(total_funds_commit_x);
|
||||
constrain_instance(total_funds_commit_y);
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
constant "DaoVoteInput" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
EcFixedPointBase NULLIFIER_K,
|
||||
}
|
||||
|
||||
contract "DaoVoteInput" {
|
||||
Base secret,
|
||||
Base serial,
|
||||
Base spend_hook,
|
||||
Base user_data,
|
||||
Base value,
|
||||
Base gov_token_id,
|
||||
Base coin_blind,
|
||||
Scalar value_blind,
|
||||
Base gov_token_blind,
|
||||
Uint32 leaf_pos,
|
||||
MerklePath path,
|
||||
Base signature_secret,
|
||||
}
|
||||
|
||||
circuit "DaoVoteInput" {
|
||||
# Poseidon hash of the nullifier
|
||||
nullifier = poseidon_hash(secret, serial);
|
||||
constrain_instance(nullifier);
|
||||
|
||||
# Pedersen commitment for coin's value
|
||||
vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
|
||||
vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
|
||||
value_commit = ec_add(vcv, vcr);
|
||||
# Since value_commit is a curve point, we fetch its coordinates
|
||||
# and constrain them:
|
||||
value_commit_x = ec_get_x(value_commit);
|
||||
value_commit_y = ec_get_y(value_commit);
|
||||
constrain_instance(value_commit_x);
|
||||
constrain_instance(value_commit_y);
|
||||
|
||||
# Commitment for coin's token ID
|
||||
token_commit = poseidon_hash(gov_token_id, gov_token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
# Coin hash
|
||||
pub = ec_mul_base(secret, NULLIFIER_K);
|
||||
pub_x = ec_get_x(pub);
|
||||
pub_y = ec_get_y(pub);
|
||||
C = poseidon_hash(pub_x, pub_y, value, gov_token_id, serial, spend_hook, user_data, coin_blind);
|
||||
|
||||
# Merkle root
|
||||
root = merkle_root(leaf_pos, path, C);
|
||||
constrain_instance(root);
|
||||
|
||||
# Finally, we derive a public key for the signature and
|
||||
# constrain its coordinates:
|
||||
signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
|
||||
signature_x = ec_get_x(signature_public);
|
||||
signature_y = ec_get_y(signature_public);
|
||||
constrain_instance(signature_x);
|
||||
constrain_instance(signature_y);
|
||||
|
||||
# At this point we've enforced all of our public inputs.
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
constant "DaoVoteMain" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
}
|
||||
|
||||
contract "DaoVoteMain" {
|
||||
# proposal params
|
||||
Base proposal_dest_x,
|
||||
Base proposal_dest_y,
|
||||
Base proposal_amount,
|
||||
Base proposal_serial,
|
||||
Base proposal_token_id,
|
||||
Base proposal_blind,
|
||||
|
||||
# DAO params
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gov_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
|
||||
# Is the vote yes or no
|
||||
Base vote_option,
|
||||
Scalar yes_vote_blind,
|
||||
|
||||
# Total amount of capital allocated to vote
|
||||
Base all_votes_value,
|
||||
Scalar all_votes_blind,
|
||||
|
||||
# Check the inputs and this proof are for the same token
|
||||
Base gov_token_blind,
|
||||
}
|
||||
|
||||
circuit "DaoVoteMain" {
|
||||
token_commit = poseidon_hash(gov_token_id, gov_token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
dao_bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
# Proposal bulla is valid means DAO bulla is also valid
|
||||
# because of dao-propose-main.zk, already checks that when
|
||||
# we first create the proposal. So it is redundant here.
|
||||
|
||||
proposal_bulla = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_serial,
|
||||
proposal_token_id,
|
||||
dao_bulla,
|
||||
proposal_blind,
|
||||
# @tmp-workaround
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(proposal_bulla);
|
||||
# TODO: we need to check the proposal isn't invalidated
|
||||
# that is expired or already executed.
|
||||
|
||||
# normally we call this yes vote
|
||||
# Pedersen commitment for vote option
|
||||
yes_votes_value = base_mul(vote_option, all_votes_value);
|
||||
yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE);
|
||||
yes_votes_blind_c = ec_mul(yes_vote_blind, VALUE_COMMIT_RANDOM);
|
||||
yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c);
|
||||
|
||||
# get curve points and constrain
|
||||
yes_votes_commit_x = ec_get_x(yes_votes_commit);
|
||||
yes_votes_commit_y = ec_get_y(yes_votes_commit);
|
||||
constrain_instance(yes_votes_commit_x);
|
||||
constrain_instance(yes_votes_commit_y);
|
||||
|
||||
# Pedersen commitment for vote value
|
||||
all_votes_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE);
|
||||
all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM);
|
||||
all_votes_commit = ec_add(all_votes_c, all_votes_blind_c);
|
||||
|
||||
# get curve points and constrain
|
||||
all_votes_commit_x = ec_get_x(all_votes_commit);
|
||||
all_votes_commit_y = ec_get_y(all_votes_commit);
|
||||
constrain_instance(all_votes_commit_x);
|
||||
constrain_instance(all_votes_commit_y);
|
||||
|
||||
# Vote option should be 0 or 1
|
||||
bool_check(vote_option);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
constant "DaoMint" {
|
||||
}
|
||||
|
||||
contract "DaoMint" {
|
||||
Base a,
|
||||
Base b,
|
||||
}
|
||||
|
||||
circuit "DaoMint" {
|
||||
c = base_add(a, b);
|
||||
constrain_instance(c);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
pub mod validate;
|
||||
pub mod wallet;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
/* 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 std::any::{Any, TypeId};
|
||||
|
||||
use darkfi_sdk::crypto::PublicKey;
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{Curve, Group},
|
||||
pallas,
|
||||
};
|
||||
|
||||
use darkfi::{
|
||||
crypto::{coin::Coin, types::DrkCircuitField},
|
||||
Error as DarkFiError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::{dao, dao::CONTRACT_ID, money},
|
||||
util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
|
||||
#[error("InvalidNumberOfFuncCalls")]
|
||||
InvalidNumberOfFuncCalls,
|
||||
|
||||
#[error("InvalidIndex")]
|
||||
InvalidIndex,
|
||||
|
||||
#[error("InvalidCallData")]
|
||||
InvalidCallData,
|
||||
|
||||
#[error("InvalidNumberOfOutputs")]
|
||||
InvalidNumberOfOutputs,
|
||||
|
||||
#[error("InvalidOutput")]
|
||||
InvalidOutput,
|
||||
|
||||
#[error("InvalidValueCommit")]
|
||||
InvalidValueCommit,
|
||||
|
||||
#[error("InvalidVoteCommit")]
|
||||
InvalidVoteCommit,
|
||||
}
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
pub proposal: pallas::Base,
|
||||
pub coin_0: pallas::Base,
|
||||
pub coin_1: pallas::Base,
|
||||
pub yes_votes_commit: pallas::Point,
|
||||
pub all_votes_commit: pallas::Point,
|
||||
pub input_value_commit: pallas::Point,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
let yes_votes_commit_coords = self.yes_votes_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let all_votes_commit_coords = self.all_votes_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let input_value_commit_coords = self.input_value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
vec![(
|
||||
"dao-exec".to_string(),
|
||||
vec![
|
||||
self.proposal,
|
||||
self.coin_0,
|
||||
self.coin_1,
|
||||
*yes_votes_commit_coords.x(),
|
||||
*yes_votes_commit_coords.y(),
|
||||
*all_votes_commit_coords.x(),
|
||||
*all_votes_commit_coords.y(),
|
||||
*input_value_commit_coords.x(),
|
||||
*input_value_commit_coords.y(),
|
||||
*super::FUNC_ID,
|
||||
pallas::Base::from(0),
|
||||
pallas::Base::from(0),
|
||||
],
|
||||
)]
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
// Enforce tx has correct format:
|
||||
// 1. There should only be 2 func_call's
|
||||
if parent_tx.func_calls.len() != 2 {
|
||||
return Err(Error::InvalidNumberOfFuncCalls)
|
||||
}
|
||||
|
||||
// 2. func_call_index == 1
|
||||
if func_call_index != 1 {
|
||||
return Err(Error::InvalidIndex)
|
||||
}
|
||||
|
||||
// 3. First item should be a Money::transfer() calldata
|
||||
if parent_tx.func_calls[0].func_id != *money::transfer::FUNC_ID {
|
||||
return Err(Error::InvalidCallData)
|
||||
}
|
||||
|
||||
let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any();
|
||||
let money_transfer_call_data =
|
||||
money_transfer_call_data.downcast_ref::<money::transfer::validate::CallData>();
|
||||
let money_transfer_call_data = money_transfer_call_data.unwrap();
|
||||
assert_eq!(
|
||||
money_transfer_call_data.type_id(),
|
||||
TypeId::of::<money::transfer::validate::CallData>()
|
||||
);
|
||||
|
||||
// 4. Money::transfer() has exactly 2 outputs
|
||||
if money_transfer_call_data.outputs.len() != 2 {
|
||||
return Err(Error::InvalidNumberOfOutputs)
|
||||
}
|
||||
|
||||
// Checks:
|
||||
// 1. Check both coins in Money::transfer() are equal to our coin_0, coin_1
|
||||
if money_transfer_call_data.outputs[0].revealed.coin != Coin(call_data.coin_0) {
|
||||
return Err(Error::InvalidOutput)
|
||||
}
|
||||
//if money_transfer_call_data.outputs[1].revealed.coin != Coin(call_data.coin_1) {
|
||||
// return Err(Error::InvalidOutput)
|
||||
//}
|
||||
|
||||
// 2. sum of Money::transfer() calldata input_value_commits == our input value commit
|
||||
let mut input_value_commits = pallas::Point::identity();
|
||||
for input in &money_transfer_call_data.inputs {
|
||||
input_value_commits += input.revealed.value_commit;
|
||||
}
|
||||
if input_value_commits != call_data.input_value_commit {
|
||||
return Err(Error::InvalidValueCommit)
|
||||
}
|
||||
|
||||
// 3. get the ProposalVote from DAO::State
|
||||
let state =
|
||||
states.lookup::<dao::State>(*CONTRACT_ID).expect("Return type is not of type State");
|
||||
let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap();
|
||||
|
||||
// 4. check yes_votes_commit is the same as in ProposalVote
|
||||
if proposal_votes.yes_votes_commit != call_data.yes_votes_commit {
|
||||
return Err(Error::InvalidVoteCommit)
|
||||
}
|
||||
// 5. also check all_votes_commit
|
||||
if proposal_votes.all_votes_commit != call_data.all_votes_commit {
|
||||
return Err(Error::InvalidVoteCommit)
|
||||
}
|
||||
|
||||
Ok(Box::new(Update { proposal: call_data.proposal }))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
pub proposal: pallas::Base,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states
|
||||
.lookup_mut::<dao::State>(*CONTRACT_ID)
|
||||
.expect("Return type is not of type State");
|
||||
state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/* 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::{pedersen::pedersen_commitment_u64, poseidon_hash, SecretKey};
|
||||
use halo2_proofs::circuit::Value;
|
||||
use log::debug;
|
||||
use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::Proof,
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::dao::{
|
||||
exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID,
|
||||
},
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
pub struct Builder {
|
||||
pub proposal: Proposal,
|
||||
pub dao: DaoParams,
|
||||
pub yes_votes_value: u64,
|
||||
pub all_votes_value: u64,
|
||||
pub yes_votes_blind: pallas::Scalar,
|
||||
pub all_votes_blind: pallas::Scalar,
|
||||
pub user_serial: pallas::Base,
|
||||
pub user_coin_blind: pallas::Base,
|
||||
pub dao_serial: pallas::Base,
|
||||
pub dao_coin_blind: pallas::Base,
|
||||
pub input_value: u64,
|
||||
pub input_value_blind: pallas::Scalar,
|
||||
pub hook_dao_exec: pallas::Base,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
|
||||
debug!(target: "dao_contract::exec::wallet::Builder", "build()");
|
||||
let mut proofs = vec![];
|
||||
|
||||
let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy();
|
||||
|
||||
let proposal_amount = pallas::Base::from(self.proposal.amount);
|
||||
|
||||
let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
|
||||
let dao_quorum = pallas::Base::from(self.dao.quorum);
|
||||
let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
|
||||
let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
|
||||
|
||||
let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy();
|
||||
|
||||
let user_spend_hook = pallas::Base::from(0);
|
||||
let user_data = pallas::Base::from(0);
|
||||
let input_value = pallas::Base::from(self.input_value);
|
||||
let change = input_value - proposal_amount;
|
||||
|
||||
let dao_bulla = poseidon_hash::<8>([
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
self.dao.gov_token_id.inner(),
|
||||
dao_pub_x,
|
||||
dao_pub_y,
|
||||
self.dao.bulla_blind,
|
||||
]);
|
||||
|
||||
let proposal_bulla = poseidon_hash::<8>([
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
self.proposal.serial,
|
||||
self.proposal.token_id.inner(),
|
||||
dao_bulla,
|
||||
self.proposal.blind,
|
||||
// @tmp-workaround
|
||||
self.proposal.blind,
|
||||
]);
|
||||
|
||||
let coin_0 = poseidon_hash::<8>([
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
self.proposal.token_id.inner(),
|
||||
self.proposal.serial,
|
||||
user_spend_hook,
|
||||
user_data,
|
||||
self.proposal.blind,
|
||||
]);
|
||||
|
||||
let coin_1 = poseidon_hash::<8>([
|
||||
dao_pub_x,
|
||||
dao_pub_y,
|
||||
change,
|
||||
self.proposal.token_id.inner(),
|
||||
self.dao_serial,
|
||||
self.hook_dao_exec,
|
||||
dao_bulla,
|
||||
self.dao_coin_blind,
|
||||
]);
|
||||
|
||||
let yes_votes_commit = pedersen_commitment_u64(self.yes_votes_value, self.yes_votes_blind);
|
||||
let yes_votes_commit_coords = yes_votes_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let all_votes_commit = pedersen_commitment_u64(self.all_votes_value, self.all_votes_blind);
|
||||
let all_votes_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let input_value_commit = pedersen_commitment_u64(self.input_value, self.input_value_blind);
|
||||
let input_value_commit_coords = input_value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-exec".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
//
|
||||
// proposal params
|
||||
Witness::Base(Value::known(proposal_dest_x)),
|
||||
Witness::Base(Value::known(proposal_dest_y)),
|
||||
Witness::Base(Value::known(proposal_amount)),
|
||||
Witness::Base(Value::known(self.proposal.serial)),
|
||||
Witness::Base(Value::known(self.proposal.token_id.inner())),
|
||||
Witness::Base(Value::known(self.proposal.blind)),
|
||||
// DAO params
|
||||
Witness::Base(Value::known(dao_proposer_limit)),
|
||||
Witness::Base(Value::known(dao_quorum)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_quot)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_base)),
|
||||
Witness::Base(Value::known(self.dao.gov_token_id.inner())),
|
||||
Witness::Base(Value::known(dao_pub_x)),
|
||||
Witness::Base(Value::known(dao_pub_y)),
|
||||
Witness::Base(Value::known(self.dao.bulla_blind)),
|
||||
// votes
|
||||
Witness::Base(Value::known(pallas::Base::from(self.yes_votes_value))),
|
||||
Witness::Base(Value::known(pallas::Base::from(self.all_votes_value))),
|
||||
Witness::Scalar(Value::known(self.yes_votes_blind)),
|
||||
Witness::Scalar(Value::known(self.all_votes_blind)),
|
||||
// outputs + inputs
|
||||
Witness::Base(Value::known(self.user_serial)),
|
||||
Witness::Base(Value::known(self.user_coin_blind)),
|
||||
Witness::Base(Value::known(self.dao_serial)),
|
||||
Witness::Base(Value::known(self.dao_coin_blind)),
|
||||
Witness::Base(Value::known(input_value)),
|
||||
Witness::Scalar(Value::known(self.input_value_blind)),
|
||||
// misc
|
||||
Witness::Base(Value::known(self.hook_dao_exec)),
|
||||
Witness::Base(Value::known(user_spend_hook)),
|
||||
Witness::Base(Value::known(user_data)),
|
||||
];
|
||||
|
||||
let public_inputs = vec![
|
||||
proposal_bulla,
|
||||
coin_0,
|
||||
coin_1,
|
||||
*yes_votes_commit_coords.x(),
|
||||
*yes_votes_commit_coords.y(),
|
||||
*all_votes_commit_coords.x(),
|
||||
*all_votes_commit_coords.y(),
|
||||
*input_value_commit_coords.x(),
|
||||
*input_value_commit_coords.y(),
|
||||
self.hook_dao_exec,
|
||||
user_spend_hook,
|
||||
user_data,
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::exec() proving error!)");
|
||||
proofs.push(input_proof);
|
||||
|
||||
let call_data = CallData {
|
||||
proposal: proposal_bulla,
|
||||
coin_0,
|
||||
coin_1,
|
||||
yes_votes_commit,
|
||||
all_votes_commit,
|
||||
input_value_commit,
|
||||
};
|
||||
|
||||
FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
pub mod validate;
|
||||
/// This is an anonymous contract function that mutates the internal DAO state.
|
||||
///
|
||||
/// Corresponds to `mint(proposer_limit, quorum, approval_ratio, dao_pubkey, dao_blind)`
|
||||
///
|
||||
/// The prover creates a `Builder`, which then constructs the `Tx` that the verifier can
|
||||
/// check using `state_transition()`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `proposer_limit` - Number of governance tokens that holder must possess in order to
|
||||
/// propose a new vote.
|
||||
/// * `quorum` - Number of minimum votes that must be met for a proposal to pass.
|
||||
/// * `approval_ratio` - Ratio of winning to total votes for a proposal to pass.
|
||||
/// * `dao_pubkey` - Public key of the DAO for permissioned access. This can also be
|
||||
/// shared publicly if you want a full decentralized DAO.
|
||||
/// * `dao_blind` - Blinding factor for the DAO bulla.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// let dao_proposer_limit = 110;
|
||||
/// let dao_quorum = 110;
|
||||
/// let dao_approval_ratio = 2;
|
||||
///
|
||||
/// let builder = dao_contract::Mint::Builder(
|
||||
/// dao_proposer_limit,
|
||||
/// dao_quorum,
|
||||
/// dao_approval_ratio,
|
||||
/// gov_token_id,
|
||||
/// dao_pubkey,
|
||||
/// dao_blind
|
||||
/// );
|
||||
/// let tx = builder.build();
|
||||
/// ```
|
||||
pub mod wallet;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/* 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 std::any::{Any, TypeId};
|
||||
|
||||
use darkfi::crypto::types::DrkCircuitField;
|
||||
use darkfi_sdk::crypto::PublicKey;
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
use crate::{
|
||||
contract::dao::{DaoBulla, State, CONTRACT_ID},
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
pub fn state_transition(
|
||||
_states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
Ok(Box::new(Update { dao_bulla: call_data.dao_bulla.clone() }))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
pub dao_bulla: DaoBulla,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry) {
|
||||
// Lookup dao_contract state from registry
|
||||
let state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
|
||||
// Add dao_bulla to state.dao_bullas
|
||||
state.add_dao_bulla(self.dao_bulla);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
pub dao_bulla: DaoBulla,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
vec![("dao-mint".to_string(), vec![self.dao_bulla.0])]
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/* 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::{poseidon_hash, PublicKey, SecretKey, TokenId};
|
||||
use halo2_proofs::circuit::Value;
|
||||
use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::Proof,
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
|
||||
use dao_contract::{DaoBulla, DaoFunction, DaoMintParams};
|
||||
|
||||
use crate::{
|
||||
contract::dao::CONTRACT_ID,
|
||||
util::{ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DaoParams {
|
||||
pub proposer_limit: u64,
|
||||
pub quorum: u64,
|
||||
pub approval_ratio_quot: u64,
|
||||
pub approval_ratio_base: u64,
|
||||
pub gov_token_id: TokenId,
|
||||
pub public_key: PublicKey,
|
||||
pub bulla_blind: pallas::Base,
|
||||
}
|
||||
|
||||
pub struct Builder {
|
||||
pub dao_proposer_limit: u64,
|
||||
pub dao_quorum: u64,
|
||||
pub dao_approval_ratio_quot: u64,
|
||||
pub dao_approval_ratio_base: u64,
|
||||
pub gov_token_id: TokenId,
|
||||
pub dao_pubkey: PublicKey,
|
||||
pub dao_bulla_blind: pallas::Base,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
/// Consumes self, and produces the function call
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> (DaoMintParams, Vec<Proof>) {
|
||||
// Dao bulla
|
||||
let dao_proposer_limit = pallas::Base::from(self.dao_proposer_limit);
|
||||
let dao_quorum = pallas::Base::from(self.dao_quorum);
|
||||
let dao_approval_ratio_quot = pallas::Base::from(self.dao_approval_ratio_quot);
|
||||
let dao_approval_ratio_base = pallas::Base::from(self.dao_approval_ratio_base);
|
||||
|
||||
let (dao_pub_x, dao_pub_y) = self.dao_pubkey.xy();
|
||||
|
||||
let dao_bulla = poseidon_hash::<8>([
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
self.gov_token_id.inner(),
|
||||
dao_pub_x,
|
||||
dao_pub_y,
|
||||
self.dao_bulla_blind,
|
||||
]);
|
||||
let dao_bulla = DaoBulla(dao_bulla);
|
||||
|
||||
// Now create the mint proof
|
||||
let zk_info = zk_bins.lookup(&"dao-mint".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(dao_proposer_limit)),
|
||||
Witness::Base(Value::known(dao_quorum)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_quot)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_base)),
|
||||
Witness::Base(Value::known(self.gov_token_id.inner())),
|
||||
Witness::Base(Value::known(dao_pub_x)),
|
||||
Witness::Base(Value::known(dao_pub_y)),
|
||||
Witness::Base(Value::known(self.dao_bulla_blind)),
|
||||
];
|
||||
let public_inputs = vec![dao_bulla.0];
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let mint_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::mint() proving error!");
|
||||
|
||||
/*
|
||||
let call_data = CallData { dao_bulla };
|
||||
FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs: vec![mint_proof],
|
||||
}
|
||||
*/
|
||||
(DaoMintParams { dao_bulla }, vec![mint_proof])
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
// mint()
|
||||
pub mod mint;
|
||||
// propose()
|
||||
pub mod propose;
|
||||
// vote{}
|
||||
pub mod vote;
|
||||
// exec{}
|
||||
pub mod exec;
|
||||
|
||||
pub mod state;
|
||||
|
||||
pub use state::{DaoBulla, State};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
pub mod validate;
|
||||
pub mod wallet;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/* 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 std::any::{Any, TypeId};
|
||||
|
||||
use darkfi_sdk::crypto::{MerkleNode, PublicKey};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
use log::error;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{Curve, Group},
|
||||
pallas,
|
||||
};
|
||||
|
||||
use darkfi::{crypto::types::DrkCircuitField, Error as DarkFiError};
|
||||
|
||||
use crate::{
|
||||
contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState},
|
||||
note::EncryptedNote2,
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
// used for debugging
|
||||
// const TARGET: &str = "dao_contract::propose::validate::state_transition()";
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid input merkle root")]
|
||||
InvalidInputMerkleRoot,
|
||||
|
||||
#[error("Invalid DAO merkle root")]
|
||||
InvalidDaoMerkleRoot,
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
pub header: Header,
|
||||
pub inputs: Vec<Input>,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
let mut zk_publics = Vec::new();
|
||||
let mut total_funds_commit = pallas::Point::identity();
|
||||
|
||||
assert!(!self.inputs.is_empty(), "inputs length cannot be zero");
|
||||
for input in &self.inputs {
|
||||
total_funds_commit += input.value_commit;
|
||||
let value_coords = input.value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let (sig_x, sig_y) = input.signature_public.xy();
|
||||
|
||||
zk_publics.push((
|
||||
"dao-propose-burn".to_string(),
|
||||
vec![
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
self.header.token_commit,
|
||||
input.merkle_root.inner(),
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
|
||||
zk_publics.push((
|
||||
"dao-propose-main".to_string(),
|
||||
vec![
|
||||
self.header.token_commit,
|
||||
self.header.dao_merkle_root.inner(),
|
||||
self.header.proposal_bulla,
|
||||
*total_funds_coords.x(),
|
||||
*total_funds_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
zk_publics
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
let mut signature_public_keys = vec![];
|
||||
for input in self.inputs.clone() {
|
||||
signature_public_keys.push(input.signature_public);
|
||||
}
|
||||
signature_public_keys
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Header {
|
||||
pub dao_merkle_root: MerkleNode,
|
||||
pub token_commit: pallas::Base,
|
||||
pub proposal_bulla: pallas::Base,
|
||||
pub enc_note: EncryptedNote2,
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
pub value_commit: pallas::Point,
|
||||
pub merkle_root: MerkleNode,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
// Check the merkle roots for the input coins are valid
|
||||
for input in &call_data.inputs {
|
||||
let money_state = states.lookup::<MoneyState>(*money::CONTRACT_ID).unwrap();
|
||||
if !money_state.is_valid_merkle(&input.merkle_root) {
|
||||
return Err(Error::InvalidInputMerkleRoot)
|
||||
}
|
||||
}
|
||||
|
||||
let state = states.lookup::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
|
||||
// Is the DAO bulla generated in the ZK proof valid
|
||||
if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) {
|
||||
return Err(Error::InvalidDaoMerkleRoot)
|
||||
}
|
||||
|
||||
// TODO: look at gov tokens avoid using already spent ones
|
||||
// Need to spend original coin and generate 2 nullifiers?
|
||||
|
||||
Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla }))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
pub proposal_bulla: pallas::Base,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states.lookup_mut::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
state.add_proposal_bulla(self.proposal_bulla);
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
/* 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::{
|
||||
pedersen::pedersen_commitment_u64, poseidon_hash, MerkleNode, PublicKey, SecretKey, TokenId,
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
use halo2_proofs::circuit::Value;
|
||||
use incrementalmerkletree::Hashable;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{ff::Field, Curve},
|
||||
pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::Proof,
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::{
|
||||
dao::{
|
||||
mint::wallet::DaoParams,
|
||||
propose::validate::{CallData, Header, Input},
|
||||
CONTRACT_ID,
|
||||
},
|
||||
money,
|
||||
},
|
||||
note,
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Note {
|
||||
pub proposal: Proposal,
|
||||
}
|
||||
|
||||
pub struct BuilderInput {
|
||||
pub secret: SecretKey,
|
||||
pub note: money::transfer::wallet::Note,
|
||||
pub leaf_position: incrementalmerkletree::Position,
|
||||
pub merkle_path: Vec<MerkleNode>,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable, Clone)]
|
||||
pub struct Proposal {
|
||||
pub dest: PublicKey,
|
||||
pub amount: u64,
|
||||
pub serial: pallas::Base,
|
||||
pub token_id: TokenId,
|
||||
pub blind: pallas::Base,
|
||||
}
|
||||
|
||||
pub struct Builder {
|
||||
pub inputs: Vec<BuilderInput>,
|
||||
pub proposal: Proposal,
|
||||
pub dao: DaoParams,
|
||||
pub dao_leaf_position: incrementalmerkletree::Position,
|
||||
pub dao_merkle_path: Vec<MerkleNode>,
|
||||
pub dao_merkle_root: MerkleNode,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
|
||||
let mut proofs = vec![];
|
||||
|
||||
let gov_token_blind = pallas::Base::random(&mut OsRng);
|
||||
|
||||
let mut inputs = vec![];
|
||||
let mut total_funds = 0;
|
||||
let mut total_funds_blinds = pallas::Scalar::from(0);
|
||||
|
||||
for input in self.inputs {
|
||||
let funds_blind = pallas::Scalar::random(&mut OsRng);
|
||||
total_funds += input.note.value;
|
||||
total_funds_blinds += funds_blind;
|
||||
|
||||
let signature_public = PublicKey::from_secret(input.signature_secret);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-propose-burn".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
// Note from the previous output
|
||||
let note = input.note;
|
||||
let leaf_pos: u64 = input.leaf_position.into();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(input.secret.inner())),
|
||||
Witness::Base(Value::known(note.serial)),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(note.value))),
|
||||
Witness::Base(Value::known(note.token_id.inner())),
|
||||
Witness::Base(Value::known(note.coin_blind)),
|
||||
Witness::Scalar(Value::known(funds_blind)),
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
|
||||
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
|
||||
Witness::Base(Value::known(input.signature_secret.inner())),
|
||||
];
|
||||
|
||||
let public_key = PublicKey::from_secret(input.secret);
|
||||
let (pub_x, pub_y) = public_key.xy();
|
||||
|
||||
let coin = poseidon_hash::<8>([
|
||||
pub_x,
|
||||
pub_y,
|
||||
pallas::Base::from(note.value),
|
||||
note.token_id.inner(),
|
||||
note.serial,
|
||||
pallas::Base::from(0),
|
||||
pallas::Base::from(0),
|
||||
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(), ¤t, sibling)
|
||||
} else {
|
||||
MerkleNode::combine(level.into(), sibling, ¤t)
|
||||
};
|
||||
}
|
||||
current
|
||||
};
|
||||
|
||||
let token_commit = poseidon_hash::<2>([note.token_id.inner(), gov_token_blind]);
|
||||
assert_eq!(self.dao.gov_token_id, note.token_id);
|
||||
|
||||
let value_commit = pedersen_commitment_u64(note.value, funds_blind);
|
||||
let value_coords = value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let (sig_x, sig_y) = signature_public.xy();
|
||||
|
||||
let public_inputs = vec![
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
token_commit,
|
||||
merkle_root.inner(),
|
||||
sig_x,
|
||||
sig_y,
|
||||
];
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::propose() proving error!");
|
||||
proofs.push(input_proof);
|
||||
|
||||
let input = Input { value_commit, merkle_root, signature_public };
|
||||
inputs.push(input);
|
||||
}
|
||||
|
||||
let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds);
|
||||
let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
|
||||
let total_funds = pallas::Base::from(total_funds);
|
||||
|
||||
let token_commit = poseidon_hash::<2>([self.dao.gov_token_id.inner(), gov_token_blind]);
|
||||
|
||||
let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy();
|
||||
|
||||
let proposal_amount = pallas::Base::from(self.proposal.amount);
|
||||
|
||||
let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
|
||||
let dao_quorum = pallas::Base::from(self.dao.quorum);
|
||||
let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
|
||||
let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
|
||||
|
||||
let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy();
|
||||
|
||||
let dao_bulla = poseidon_hash::<8>([
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
self.dao.gov_token_id.inner(),
|
||||
dao_pub_x,
|
||||
dao_pub_y,
|
||||
self.dao.bulla_blind,
|
||||
]);
|
||||
|
||||
let dao_leaf_position: u64 = self.dao_leaf_position.into();
|
||||
|
||||
let proposal_bulla = poseidon_hash::<8>([
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
self.proposal.serial,
|
||||
self.proposal.token_id.inner(),
|
||||
dao_bulla,
|
||||
self.proposal.blind,
|
||||
// @tmp-workaround
|
||||
self.proposal.blind,
|
||||
]);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-propose-main".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
let prover_witnesses = vec![
|
||||
// Proposers total number of gov tokens
|
||||
Witness::Base(Value::known(total_funds)),
|
||||
Witness::Scalar(Value::known(total_funds_blinds)),
|
||||
// Used for blinding exported gov token ID
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
// proposal params
|
||||
Witness::Base(Value::known(proposal_dest_x)),
|
||||
Witness::Base(Value::known(proposal_dest_y)),
|
||||
Witness::Base(Value::known(proposal_amount)),
|
||||
Witness::Base(Value::known(self.proposal.serial)),
|
||||
Witness::Base(Value::known(self.proposal.token_id.inner())),
|
||||
Witness::Base(Value::known(self.proposal.blind)),
|
||||
// DAO params
|
||||
Witness::Base(Value::known(dao_proposer_limit)),
|
||||
Witness::Base(Value::known(dao_quorum)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_quot)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_base)),
|
||||
Witness::Base(Value::known(self.dao.gov_token_id.inner())),
|
||||
Witness::Base(Value::known(dao_pub_x)),
|
||||
Witness::Base(Value::known(dao_pub_y)),
|
||||
Witness::Base(Value::known(self.dao.bulla_blind)),
|
||||
Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())),
|
||||
Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())),
|
||||
];
|
||||
let public_inputs = vec![
|
||||
token_commit,
|
||||
self.dao_merkle_root.inner(),
|
||||
proposal_bulla,
|
||||
*total_funds_coords.x(),
|
||||
*total_funds_coords.y(),
|
||||
];
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::propose() proving error!");
|
||||
proofs.push(main_proof);
|
||||
|
||||
let note = Note { proposal: self.proposal };
|
||||
let enc_note = note::encrypt(¬e, &self.dao.public_key).unwrap();
|
||||
let header = Header {
|
||||
dao_merkle_root: self.dao_merkle_root,
|
||||
proposal_bulla,
|
||||
token_commit,
|
||||
enc_note,
|
||||
};
|
||||
|
||||
let call_data = CallData { header, inputs };
|
||||
|
||||
FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/* 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 std::{any::Any, collections::HashMap};
|
||||
|
||||
use darkfi_sdk::crypto::{constants::MERKLE_DEPTH, MerkleNode, Nullifier};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
|
||||
use pasta_curves::{group::Group, pallas};
|
||||
|
||||
use crate::util::HashableBase;
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoBulla(pub pallas::Base);
|
||||
|
||||
type MerkleTree = BridgeTree<MerkleNode, { MERKLE_DEPTH }>;
|
||||
|
||||
pub struct ProposalVotes {
|
||||
// TODO: might be more logical to have 'yes_votes_commit' and 'no_votes_commit'
|
||||
/// Weighted vote commit
|
||||
pub yes_votes_commit: pallas::Point,
|
||||
/// All value staked in the vote
|
||||
pub all_votes_commit: pallas::Point,
|
||||
/// Vote nullifiers
|
||||
pub vote_nulls: Vec<Nullifier>,
|
||||
}
|
||||
|
||||
impl ProposalVotes {
|
||||
pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||
self.vote_nulls.iter().any(|n| n == nullifier)
|
||||
}
|
||||
}
|
||||
|
||||
/// This DAO state is for all DAOs on the network. There should only be a single instance.
|
||||
pub struct State {
|
||||
dao_bullas: Vec<DaoBulla>,
|
||||
pub dao_tree: MerkleTree,
|
||||
pub dao_roots: Vec<MerkleNode>,
|
||||
|
||||
//proposal_bullas: Vec<pallas::Base>,
|
||||
pub proposal_tree: MerkleTree,
|
||||
pub proposal_roots: Vec<MerkleNode>,
|
||||
pub proposal_votes: HashMap<HashableBase, ProposalVotes>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Box<dyn Any + Send> {
|
||||
Box::new(Self {
|
||||
dao_bullas: Vec::new(),
|
||||
dao_tree: MerkleTree::new(100),
|
||||
dao_roots: Vec::new(),
|
||||
//proposal_bullas: Vec::new(),
|
||||
proposal_tree: MerkleTree::new(100),
|
||||
proposal_roots: Vec::new(),
|
||||
proposal_votes: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_dao_bulla(&mut self, bulla: DaoBulla) {
|
||||
let node = MerkleNode::from(bulla.0);
|
||||
self.dao_bullas.push(bulla);
|
||||
self.dao_tree.append(&node);
|
||||
self.dao_roots.push(self.dao_tree.root(0).unwrap());
|
||||
}
|
||||
|
||||
pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) {
|
||||
let node = MerkleNode::from(bulla);
|
||||
//self.proposal_bullas.push(bulla);
|
||||
self.proposal_tree.append(&node);
|
||||
self.proposal_roots.push(self.proposal_tree.root(0).unwrap());
|
||||
self.proposal_votes.insert(
|
||||
HashableBase(bulla),
|
||||
ProposalVotes {
|
||||
yes_votes_commit: pallas::Point::identity(),
|
||||
all_votes_commit: pallas::Point::identity(),
|
||||
vote_nulls: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn lookup_proposal_votes(&self, proposal_bulla: pallas::Base) -> Option<&ProposalVotes> {
|
||||
self.proposal_votes.get(&HashableBase(proposal_bulla))
|
||||
}
|
||||
pub fn lookup_proposal_votes_mut(
|
||||
&mut self,
|
||||
proposal_bulla: pallas::Base,
|
||||
) -> Option<&mut ProposalVotes> {
|
||||
self.proposal_votes.get_mut(&HashableBase(proposal_bulla))
|
||||
}
|
||||
|
||||
pub fn is_valid_dao_merkle(&self, root: &MerkleNode) -> bool {
|
||||
self.dao_roots.iter().any(|m| m == root)
|
||||
}
|
||||
|
||||
// TODO: This never gets called.
|
||||
pub fn _is_valid_proposal_merkle(&self, root: &MerkleNode) -> bool {
|
||||
self.proposal_roots.iter().any(|m| m == root)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
pub mod validate;
|
||||
pub mod wallet;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
/* 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 std::any::{Any, TypeId};
|
||||
|
||||
use darkfi_sdk::crypto::{MerkleNode, Nullifier, PublicKey};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
use log::error;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{Curve, Group},
|
||||
pallas,
|
||||
};
|
||||
|
||||
use darkfi::{crypto::types::DrkCircuitField, Error as DarkFiError};
|
||||
|
||||
use crate::{
|
||||
contract::{dao, dao::State as DaoState, money, money::state::State as MoneyState},
|
||||
note::EncryptedNote2,
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid proposal")]
|
||||
InvalidProposal,
|
||||
|
||||
#[error("Voting with already spent coinage")]
|
||||
SpentCoin,
|
||||
|
||||
#[error("Double voting")]
|
||||
DoubleVote,
|
||||
|
||||
#[error("Invalid input merkle root")]
|
||||
InvalidInputMerkleRoot,
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
pub header: Header,
|
||||
pub inputs: Vec<Input>,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
let mut zk_publics = Vec::new();
|
||||
let mut all_votes_commit = pallas::Point::identity();
|
||||
|
||||
assert!(!self.inputs.is_empty(), "inputs length cannot be zero");
|
||||
for input in &self.inputs {
|
||||
all_votes_commit += input.vote_commit;
|
||||
let value_coords = input.vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let (sig_x, sig_y) = input.signature_public.xy();
|
||||
|
||||
zk_publics.push((
|
||||
"dao-vote-burn".to_string(),
|
||||
vec![
|
||||
input.nullifier.inner(),
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
self.header.token_commit,
|
||||
input.merkle_root.inner(),
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let yes_vote_commit_coords = self.header.yes_vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_publics.push((
|
||||
"dao-vote-main".to_string(),
|
||||
vec![
|
||||
self.header.token_commit,
|
||||
self.header.proposal_bulla,
|
||||
*yes_vote_commit_coords.x(),
|
||||
*yes_vote_commit_coords.y(),
|
||||
*vote_commit_coords.x(),
|
||||
*vote_commit_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
zk_publics
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
let mut signature_public_keys = vec![];
|
||||
for input in self.inputs.clone() {
|
||||
signature_public_keys.push(input.signature_public);
|
||||
}
|
||||
signature_public_keys
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Header {
|
||||
pub token_commit: pallas::Base,
|
||||
pub proposal_bulla: pallas::Base,
|
||||
pub yes_vote_commit: pallas::Point,
|
||||
pub enc_note: EncryptedNote2,
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
pub nullifier: Nullifier,
|
||||
pub vote_commit: pallas::Point,
|
||||
pub merkle_root: MerkleNode,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
let dao_state = states.lookup::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
|
||||
// Check proposal_bulla exists
|
||||
let votes_info = dao_state.lookup_proposal_votes(call_data.header.proposal_bulla);
|
||||
if votes_info.is_none() {
|
||||
return Err(Error::InvalidProposal)
|
||||
}
|
||||
let votes_info = votes_info.unwrap();
|
||||
|
||||
// Check the merkle roots for the input coins are valid
|
||||
let mut vote_nulls = Vec::new();
|
||||
let mut all_vote_commit = pallas::Point::identity();
|
||||
for input in &call_data.inputs {
|
||||
let money_state = states.lookup::<MoneyState>(*money::CONTRACT_ID).unwrap();
|
||||
if !money_state.is_valid_merkle(&input.merkle_root) {
|
||||
return Err(Error::InvalidInputMerkleRoot)
|
||||
}
|
||||
|
||||
if money_state.nullifier_exists(&input.nullifier) {
|
||||
return Err(Error::SpentCoin)
|
||||
}
|
||||
|
||||
if votes_info.nullifier_exists(&input.nullifier) {
|
||||
return Err(Error::DoubleVote)
|
||||
}
|
||||
|
||||
all_vote_commit += input.vote_commit;
|
||||
|
||||
vote_nulls.push(input.nullifier);
|
||||
}
|
||||
|
||||
Ok(Box::new(Update {
|
||||
proposal_bulla: call_data.header.proposal_bulla,
|
||||
vote_nulls,
|
||||
yes_vote_commit: call_data.header.yes_vote_commit,
|
||||
all_vote_commit,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
proposal_bulla: pallas::Base,
|
||||
vote_nulls: Vec<Nullifier>,
|
||||
pub yes_vote_commit: pallas::Point,
|
||||
pub all_vote_commit: pallas::Point,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states.lookup_mut::<DaoState>(*dao::CONTRACT_ID).unwrap();
|
||||
let votes_info = state.lookup_proposal_votes_mut(self.proposal_bulla).unwrap();
|
||||
votes_info.yes_votes_commit += self.yes_vote_commit;
|
||||
votes_info.all_votes_commit += self.all_vote_commit;
|
||||
votes_info.vote_nulls.append(&mut self.vote_nulls);
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
/* 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::{
|
||||
pedersen::pedersen_commitment_u64, poseidon_hash, Keypair, MerkleNode, Nullifier, PublicKey,
|
||||
SecretKey,
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
use halo2_proofs::circuit::Value;
|
||||
use incrementalmerkletree::Hashable;
|
||||
use log::debug;
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{ff::Field, Curve},
|
||||
pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::Proof,
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::{
|
||||
dao::{
|
||||
mint::wallet::DaoParams,
|
||||
propose::wallet::Proposal,
|
||||
vote::validate::{CallData, Header, Input},
|
||||
CONTRACT_ID,
|
||||
},
|
||||
money,
|
||||
},
|
||||
note,
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Note {
|
||||
pub vote: Vote,
|
||||
pub vote_value: u64,
|
||||
pub vote_value_blind: pallas::Scalar,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct Vote {
|
||||
pub vote_option: bool,
|
||||
pub vote_option_blind: pallas::Scalar,
|
||||
}
|
||||
|
||||
pub struct BuilderInput {
|
||||
pub secret: SecretKey,
|
||||
pub note: money::transfer::wallet::Note,
|
||||
pub leaf_position: incrementalmerkletree::Position,
|
||||
pub merkle_path: Vec<MerkleNode>,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
// TODO: should be token locking voting?
|
||||
// Inside ZKproof, check proposal is correct.
|
||||
pub struct Builder {
|
||||
pub inputs: Vec<BuilderInput>,
|
||||
pub vote: Vote,
|
||||
pub vote_keypair: Keypair,
|
||||
pub proposal: Proposal,
|
||||
pub dao: DaoParams,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
|
||||
debug!(target: "dao_contract::vote::wallet::Builder", "build()");
|
||||
let mut proofs = vec![];
|
||||
|
||||
let gov_token_blind = pallas::Base::random(&mut OsRng);
|
||||
|
||||
let mut inputs = vec![];
|
||||
let mut vote_value = 0;
|
||||
let mut vote_value_blind = pallas::Scalar::from(0);
|
||||
|
||||
for input in self.inputs {
|
||||
let value_blind = pallas::Scalar::random(&mut OsRng);
|
||||
|
||||
vote_value += input.note.value;
|
||||
vote_value_blind += value_blind;
|
||||
|
||||
let signature_public = PublicKey::from_secret(input.signature_secret);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap();
|
||||
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
// Note from the previous output
|
||||
let note = input.note;
|
||||
let leaf_pos: u64 = input.leaf_position.into();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(input.secret.inner())),
|
||||
Witness::Base(Value::known(note.serial)),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(0))),
|
||||
Witness::Base(Value::known(pallas::Base::from(note.value))),
|
||||
Witness::Base(Value::known(note.token_id.inner())),
|
||||
Witness::Base(Value::known(note.coin_blind)),
|
||||
Witness::Scalar(Value::known(vote_value_blind)),
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())),
|
||||
Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())),
|
||||
Witness::Base(Value::known(input.signature_secret.inner())),
|
||||
];
|
||||
|
||||
let public_key = PublicKey::from_secret(input.secret);
|
||||
let (pub_x, pub_y) = public_key.xy();
|
||||
|
||||
let coin = poseidon_hash::<8>([
|
||||
pub_x,
|
||||
pub_y,
|
||||
pallas::Base::from(note.value),
|
||||
note.token_id.inner(),
|
||||
note.serial,
|
||||
pallas::Base::from(0),
|
||||
pallas::Base::from(0),
|
||||
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(), ¤t, sibling)
|
||||
} else {
|
||||
MerkleNode::combine(level.into(), sibling, ¤t)
|
||||
};
|
||||
}
|
||||
current
|
||||
};
|
||||
|
||||
let token_commit = poseidon_hash::<2>([note.token_id.inner(), gov_token_blind]);
|
||||
assert_eq!(self.dao.gov_token_id, note.token_id);
|
||||
|
||||
let nullifier = poseidon_hash::<2>([input.secret.inner(), note.serial]);
|
||||
|
||||
let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind);
|
||||
let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let (sig_x, sig_y) = signature_public.xy();
|
||||
|
||||
let public_inputs = vec![
|
||||
nullifier,
|
||||
*vote_commit_coords.x(),
|
||||
*vote_commit_coords.y(),
|
||||
token_commit,
|
||||
merkle_root.inner(),
|
||||
sig_x,
|
||||
sig_y,
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
let proving_key = &zk_info.proving_key;
|
||||
debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()");
|
||||
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::vote() proving error!");
|
||||
proofs.push(input_proof);
|
||||
|
||||
let input = Input {
|
||||
nullifier: Nullifier::from(nullifier),
|
||||
vote_commit,
|
||||
merkle_root,
|
||||
signature_public,
|
||||
};
|
||||
inputs.push(input);
|
||||
}
|
||||
|
||||
let token_commit = poseidon_hash::<2>([self.dao.gov_token_id.inner(), gov_token_blind]);
|
||||
|
||||
let (proposal_dest_x, proposal_dest_y) = self.proposal.dest.xy();
|
||||
|
||||
let proposal_amount = pallas::Base::from(self.proposal.amount);
|
||||
|
||||
let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit);
|
||||
let dao_quorum = pallas::Base::from(self.dao.quorum);
|
||||
let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot);
|
||||
let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base);
|
||||
|
||||
let (dao_pub_x, dao_pub_y) = self.dao.public_key.xy();
|
||||
|
||||
let dao_bulla = poseidon_hash::<8>([
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
self.dao.gov_token_id.inner(),
|
||||
dao_pub_x,
|
||||
dao_pub_y,
|
||||
self.dao.bulla_blind,
|
||||
]);
|
||||
|
||||
let proposal_bulla = poseidon_hash::<8>([
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
self.proposal.serial,
|
||||
self.proposal.token_id.inner(),
|
||||
dao_bulla,
|
||||
self.proposal.blind,
|
||||
// @tmp-workaround
|
||||
self.proposal.blind,
|
||||
]);
|
||||
|
||||
let vote_option = self.vote.vote_option as u64;
|
||||
assert!(vote_option == 0 || vote_option == 1);
|
||||
|
||||
let yes_vote_commit =
|
||||
pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind);
|
||||
let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind);
|
||||
let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
// proposal params
|
||||
Witness::Base(Value::known(proposal_dest_x)),
|
||||
Witness::Base(Value::known(proposal_dest_y)),
|
||||
Witness::Base(Value::known(proposal_amount)),
|
||||
Witness::Base(Value::known(self.proposal.serial)),
|
||||
Witness::Base(Value::known(self.proposal.token_id.inner())),
|
||||
Witness::Base(Value::known(self.proposal.blind)),
|
||||
// DAO params
|
||||
Witness::Base(Value::known(dao_proposer_limit)),
|
||||
Witness::Base(Value::known(dao_quorum)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_quot)),
|
||||
Witness::Base(Value::known(dao_approval_ratio_base)),
|
||||
Witness::Base(Value::known(self.dao.gov_token_id.inner())),
|
||||
Witness::Base(Value::known(dao_pub_x)),
|
||||
Witness::Base(Value::known(dao_pub_y)),
|
||||
Witness::Base(Value::known(self.dao.bulla_blind)),
|
||||
// Vote
|
||||
Witness::Base(Value::known(pallas::Base::from(vote_option))),
|
||||
Witness::Scalar(Value::known(self.vote.vote_option_blind)),
|
||||
// Total number of gov tokens allocated
|
||||
Witness::Base(Value::known(pallas::Base::from(vote_value))),
|
||||
Witness::Scalar(Value::known(vote_value_blind)),
|
||||
// gov token
|
||||
Witness::Base(Value::known(gov_token_blind)),
|
||||
];
|
||||
|
||||
let public_inputs = vec![
|
||||
token_commit,
|
||||
proposal_bulla,
|
||||
// this should be a value commit??
|
||||
*yes_vote_commit_coords.x(),
|
||||
*yes_vote_commit_coords.y(),
|
||||
*all_vote_commit_coords.x(),
|
||||
*all_vote_commit_coords.y(),
|
||||
];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
|
||||
let proving_key = &zk_info.proving_key;
|
||||
debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()");
|
||||
let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("DAO::vote() proving error!");
|
||||
proofs.push(main_proof);
|
||||
|
||||
let note = Note { vote: self.vote, vote_value, vote_value_blind };
|
||||
let enc_note = note::encrypt(¬e, &self.vote_keypair.public).unwrap();
|
||||
|
||||
let header = Header { token_commit, proposal_bulla, yes_vote_commit, enc_note };
|
||||
|
||||
let call_data = CallData { header, inputs };
|
||||
|
||||
FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
pub mod validate;
|
||||
pub mod wallet;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/* 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 std::any::{Any, TypeId};
|
||||
|
||||
use darkfi::{crypto::types::DrkCircuitField, Error as DarkFiError};
|
||||
use darkfi_sdk::crypto::PublicKey;
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
use pasta_curves::pallas;
|
||||
|
||||
use crate::{
|
||||
contract::example::{state::State, CONTRACT_ID},
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("ValueExists")]
|
||||
ValueExists,
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
pub public_value: pallas::Base,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
vec![("example-foo".to_string(), vec![self.public_value])]
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
vec![self.signature_public]
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
let example_state = states.lookup::<State>(*CONTRACT_ID).unwrap();
|
||||
|
||||
if example_state.public_exists(&call_data.public_value) {
|
||||
return Err(Error::ValueExists)
|
||||
}
|
||||
|
||||
Ok(Box::new(Update { public_value: call_data.public_value }))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
public_value: pallas::Base,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry) {
|
||||
let example_state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
|
||||
example_state.add_public_value(self.public_value);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/* 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::{PublicKey, SecretKey};
|
||||
use halo2_proofs::circuit::Value;
|
||||
use log::debug;
|
||||
use pasta_curves::pallas;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::Proof,
|
||||
zk::vm::{Witness, ZkCircuit},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::example::{foo::validate::CallData, CONTRACT_ID},
|
||||
util::{FuncCall, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
pub struct Foo {
|
||||
pub a: u64,
|
||||
pub b: u64,
|
||||
}
|
||||
|
||||
pub struct Builder {
|
||||
pub foo: Foo,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall {
|
||||
debug!(target: "example_contract::foo::wallet::Builder", "build()");
|
||||
let mut proofs = vec![];
|
||||
|
||||
let zk_info = zk_bins.lookup(&"example-foo".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Binary(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not binary info")
|
||||
};
|
||||
|
||||
let zk_bin = zk_info.bincode.clone();
|
||||
|
||||
let prover_witnesses = vec![
|
||||
Witness::Base(Value::known(pallas::Base::from(self.foo.a))),
|
||||
Witness::Base(Value::known(pallas::Base::from(self.foo.b))),
|
||||
];
|
||||
|
||||
let a = pallas::Base::from(self.foo.a);
|
||||
let b = pallas::Base::from(self.foo.b);
|
||||
|
||||
let c = a + b;
|
||||
|
||||
let public_inputs = vec![c];
|
||||
|
||||
let circuit = ZkCircuit::new(prover_witnesses, zk_bin);
|
||||
debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()");
|
||||
let proving_key = &zk_info.proving_key;
|
||||
let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng)
|
||||
.expect("Example::foo() proving error!)");
|
||||
proofs.push(input_proof);
|
||||
|
||||
let signature_public = PublicKey::from_secret(self.signature_secret);
|
||||
|
||||
let call_data = CallData { public_value: c, signature_public };
|
||||
|
||||
FuncCall {
|
||||
contract_id: *CONTRACT_ID,
|
||||
func_id: *super::FUNC_ID,
|
||||
call_data: Box::new(call_data),
|
||||
proofs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
// foo()
|
||||
pub mod foo;
|
||||
|
||||
pub mod state;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/* 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 std::any::Any;
|
||||
|
||||
use pasta_curves::pallas;
|
||||
|
||||
pub struct State {
|
||||
pub public_values: Vec<pallas::Base>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new() -> Box<dyn Any + Send> {
|
||||
Box::new(Self { public_values: Vec::new() })
|
||||
}
|
||||
|
||||
pub fn add_public_value(&mut self, public_value: pallas::Base) {
|
||||
self.public_values.push(public_value)
|
||||
}
|
||||
//
|
||||
pub fn public_exists(&self, public_value: &pallas::Base) -> bool {
|
||||
self.public_values.iter().any(|v| v == public_value)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
pub mod dao;
|
||||
pub mod example;
|
||||
pub mod money;
|
||||
@@ -1,31 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
// transfer()
|
||||
pub mod transfer;
|
||||
|
||||
pub mod state;
|
||||
pub use state::State;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/* 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::{constants::MERKLE_DEPTH, MerkleNode, Nullifier, PublicKey};
|
||||
use incrementalmerkletree::bridgetree::BridgeTree;
|
||||
|
||||
type MerkleTree = BridgeTree<MerkleNode, { MERKLE_DEPTH }>;
|
||||
|
||||
/// The state machine, held in memory.
|
||||
pub struct State {
|
||||
/// The entire Merkle tree state
|
||||
pub tree: MerkleTree,
|
||||
/// List of all previous and the current Merkle roots.
|
||||
/// This is the hashed value of all the children.
|
||||
pub merkle_roots: Vec<MerkleNode>,
|
||||
/// Nullifiers prevent double spending
|
||||
pub nullifiers: Vec<Nullifier>,
|
||||
|
||||
/// Public key of the cashier
|
||||
pub cashier_signature_public: PublicKey,
|
||||
|
||||
/// Public key of the faucet
|
||||
pub faucet_signature_public: PublicKey,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(
|
||||
cashier_signature_public: PublicKey,
|
||||
faucet_signature_public: PublicKey,
|
||||
) -> Box<Self> {
|
||||
Box::new(Self {
|
||||
tree: MerkleTree::new(100),
|
||||
merkle_roots: vec![],
|
||||
nullifiers: vec![],
|
||||
cashier_signature_public,
|
||||
faucet_signature_public,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_valid_cashier_public_key(&self, public: &PublicKey) -> bool {
|
||||
public == &self.cashier_signature_public
|
||||
}
|
||||
|
||||
pub fn is_valid_faucet_public_key(&self, public: &PublicKey) -> bool {
|
||||
public == &self.faucet_signature_public
|
||||
}
|
||||
|
||||
pub fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool {
|
||||
self.merkle_roots.iter().any(|m| m == merkle_root)
|
||||
}
|
||||
|
||||
pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||
self.nullifiers.iter().any(|n| n == nullifier)
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/* 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 lazy_static::lazy_static;
|
||||
use pasta_curves::{group::ff::Field, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
pub mod validate;
|
||||
pub mod wallet;
|
||||
pub use wallet::{Builder, BuilderClearInputInfo, BuilderInputInfo, BuilderOutputInfo, Note};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
@@ -1,392 +0,0 @@
|
||||
/* 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 std::any::{Any, TypeId};
|
||||
|
||||
use darkfi_sdk::crypto::{
|
||||
pedersen::{pedersen_commitment_base, pedersen_commitment_u64},
|
||||
MerkleNode, Nullifier, PublicKey, TokenId,
|
||||
};
|
||||
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
|
||||
use incrementalmerkletree::Tree;
|
||||
use log::{debug, error};
|
||||
use pasta_curves::{group::Group, pallas};
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
coin::Coin,
|
||||
types::{DrkCircuitField, DrkValueBlind, DrkValueCommit},
|
||||
BurnRevealedValues, MintRevealedValues,
|
||||
},
|
||||
Error as DarkFiError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
contract::{
|
||||
dao,
|
||||
money::{state::State, CONTRACT_ID},
|
||||
},
|
||||
note::EncryptedNote2,
|
||||
util::{CallDataBase, StateRegistry, Transaction, UpdateBase},
|
||||
};
|
||||
|
||||
const TARGET: &str = "money_contract::transfer::validate::state_transition()";
|
||||
|
||||
/// A struct representing a state update.
|
||||
/// This gets applied on top of an existing state.
|
||||
#[derive(Clone)]
|
||||
pub struct Update {
|
||||
/// All nullifiers in a transaction
|
||||
pub nullifiers: Vec<Nullifier>,
|
||||
/// All coins in a transaction
|
||||
pub coins: Vec<Coin>,
|
||||
/// All encrypted notes in a transaction
|
||||
pub enc_notes: Vec<EncryptedNote2>,
|
||||
}
|
||||
|
||||
impl UpdateBase for Update {
|
||||
fn apply(mut self: Box<Self>, states: &mut StateRegistry) {
|
||||
let state = states.lookup_mut::<State>(*CONTRACT_ID).unwrap();
|
||||
|
||||
// Extend our list of nullifiers with the ones from the update
|
||||
state.nullifiers.append(&mut self.nullifiers);
|
||||
|
||||
//// Update merkle tree and witnesses
|
||||
for coin in self.coins {
|
||||
// Add the new coins to the Merkle tree
|
||||
let node = MerkleNode::from(coin.0);
|
||||
state.tree.append(&node);
|
||||
|
||||
// Keep track of all Merkle roots that have existed
|
||||
state.merkle_roots.push(state.tree.root(0).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_transition(
|
||||
states: &StateRegistry,
|
||||
func_call_index: usize,
|
||||
parent_tx: &Transaction,
|
||||
) -> Result<Box<dyn UpdateBase + Send>> {
|
||||
// Check the public keys in the clear inputs to see if they're coming
|
||||
// from a valid cashier or faucet.
|
||||
debug!(target: TARGET, "Iterate clear_inputs");
|
||||
let func_call = &parent_tx.func_calls[func_call_index];
|
||||
let call_data = func_call.call_data.as_any();
|
||||
|
||||
assert_eq!((&*call_data).type_id(), TypeId::of::<CallData>());
|
||||
let call_data = call_data.downcast_ref::<CallData>();
|
||||
|
||||
// This will be inside wasm so unwrap is fine.
|
||||
let call_data = call_data.unwrap();
|
||||
|
||||
let state = states.lookup::<State>(*CONTRACT_ID).expect("Return type is not of type State");
|
||||
|
||||
// Code goes here
|
||||
for (i, input) in call_data.clear_inputs.iter().enumerate() {
|
||||
let pk = &input.signature_public;
|
||||
// TODO: this depends on the token ID
|
||||
if !state.is_valid_cashier_public_key(pk) && !state.is_valid_faucet_public_key(pk) {
|
||||
error!(target: TARGET, "Invalid pubkey for clear input: {:?}", pk);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::InvalidCashierOrFaucetKey(i)))
|
||||
}
|
||||
}
|
||||
|
||||
// Nullifiers in the transaction
|
||||
let mut nullifiers = Vec::with_capacity(call_data.inputs.len());
|
||||
|
||||
debug!(target: TARGET, "Iterate inputs");
|
||||
for (i, input) in call_data.inputs.iter().enumerate() {
|
||||
let merkle = &input.revealed.merkle_root;
|
||||
|
||||
// The Merkle root is used to know whether this is a coin that
|
||||
// existed in a previous state.
|
||||
if !state.is_valid_merkle(merkle) {
|
||||
error!(target: TARGET, "Invalid Merkle root (input {})", i);
|
||||
debug!(target: TARGET, "root: {:?}", merkle);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::InvalidMerkle(i)))
|
||||
}
|
||||
|
||||
// Check the spend_hook is satisfied
|
||||
// The spend_hook says a coin must invoke another contract function when being spent
|
||||
// If the value is set, then we check the function call exists
|
||||
let spend_hook = &input.revealed.spend_hook;
|
||||
if spend_hook != &pallas::Base::from(0) {
|
||||
// spend_hook is set so we enforce the rules
|
||||
let mut is_found = false;
|
||||
for (i, func_call) in parent_tx.func_calls.iter().enumerate() {
|
||||
// Skip current func_call
|
||||
if i == func_call_index {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: we need to change these to pallas::Base
|
||||
// temporary workaround for now
|
||||
// if func_call.func_id == spend_hook ...
|
||||
if func_call.func_id == *dao::exec::FUNC_ID {
|
||||
is_found = true;
|
||||
break
|
||||
}
|
||||
}
|
||||
if !is_found {
|
||||
return Err(Error::VerifyFailed(VerifyFailed::SpendHookNotSatisfied))
|
||||
}
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist.
|
||||
// It is the double-spend protection.
|
||||
let nullifier = &input.revealed.nullifier;
|
||||
if state.nullifier_exists(nullifier) ||
|
||||
(1..nullifiers.len()).any(|i| nullifiers[i..].contains(&nullifiers[i - 1]))
|
||||
{
|
||||
error!(target: TARGET, "Duplicate nullifier found (input {})", i);
|
||||
debug!(target: TARGET, "nullifier: {:?}", nullifier);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::NullifierExists(i)))
|
||||
}
|
||||
|
||||
nullifiers.push(input.revealed.nullifier);
|
||||
}
|
||||
|
||||
debug!(target: TARGET, "Verifying call data");
|
||||
match call_data.verify() {
|
||||
Ok(()) => {
|
||||
debug!(target: TARGET, "Verified successfully")
|
||||
}
|
||||
Err(e) => {
|
||||
error!(target: TARGET, "Failed verifying zk proofs: {}", e);
|
||||
return Err(Error::VerifyFailed(VerifyFailed::ProofVerifyFailed(e.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
// Newly created coins for this transaction
|
||||
let mut coins = Vec::with_capacity(call_data.outputs.len());
|
||||
let mut enc_notes = Vec::with_capacity(call_data.outputs.len());
|
||||
|
||||
for output in &call_data.outputs {
|
||||
// Gather all the coins
|
||||
coins.push(output.revealed.coin);
|
||||
enc_notes.push(output.enc_note.clone());
|
||||
}
|
||||
|
||||
Ok(Box::new(Update { nullifiers, coins, enc_notes }))
|
||||
}
|
||||
|
||||
/// A DarkFi transaction
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct CallData {
|
||||
/// Clear inputs
|
||||
pub clear_inputs: Vec<ClearInput>,
|
||||
/// Anonymous inputs
|
||||
pub inputs: Vec<Input>,
|
||||
/// Anonymous outputs
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
impl CallDataBase for CallData {
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
|
||||
let mut public_values = Vec::new();
|
||||
for input in &self.inputs {
|
||||
public_values.push(("money-transfer-burn".to_string(), input.revealed.make_outputs()));
|
||||
}
|
||||
for output in &self.outputs {
|
||||
public_values.push(("money-transfer-mint".to_string(), output.revealed.make_outputs()));
|
||||
}
|
||||
public_values
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey> {
|
||||
let mut signature_public_keys = Vec::new();
|
||||
for input in self.clear_inputs.clone() {
|
||||
signature_public_keys.push(input.signature_public);
|
||||
}
|
||||
signature_public_keys
|
||||
}
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
mut writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error> {
|
||||
self.encode(&mut writer)
|
||||
}
|
||||
}
|
||||
impl CallData {
|
||||
/// Verify the transaction
|
||||
pub fn verify(&self) -> VerifyResult<()> {
|
||||
// must have minimum 1 clear or anon input, and 1 output
|
||||
if self.clear_inputs.len() + self.inputs.len() == 0 {
|
||||
error!("tx::verify(): Missing inputs");
|
||||
return Err(VerifyFailed::LackingInputs)
|
||||
}
|
||||
if self.outputs.len() == 0 {
|
||||
error!("tx::verify(): Missing outputs");
|
||||
return Err(VerifyFailed::LackingOutputs)
|
||||
}
|
||||
|
||||
// Accumulator for the value commitments
|
||||
let mut valcom_total = DrkValueCommit::identity();
|
||||
|
||||
// Add values from the clear inputs
|
||||
for input in &self.clear_inputs {
|
||||
valcom_total += pedersen_commitment_u64(input.value, input.value_blind);
|
||||
}
|
||||
// Add values from the inputs
|
||||
for input in &self.inputs {
|
||||
valcom_total += &input.revealed.value_commit;
|
||||
}
|
||||
// Subtract values from the outputs
|
||||
for output in &self.outputs {
|
||||
valcom_total -= &output.revealed.value_commit;
|
||||
}
|
||||
|
||||
// If the accumulator is not back in its initial state,
|
||||
// there's a value mismatch.
|
||||
if valcom_total != DrkValueCommit::identity() {
|
||||
error!("tx::verify(): Missing funds");
|
||||
return Err(VerifyFailed::MissingFunds)
|
||||
}
|
||||
|
||||
// Verify that the token commitments match
|
||||
if !self.verify_token_commitments() {
|
||||
error!("tx::verify(): Token ID mismatch");
|
||||
return Err(VerifyFailed::TokenMismatch)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_token_commitments(&self) -> bool {
|
||||
assert_ne!(self.outputs.len(), 0);
|
||||
let token_commit_value = self.outputs[0].revealed.token_commit;
|
||||
|
||||
let mut failed =
|
||||
self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value);
|
||||
|
||||
failed = failed ||
|
||||
self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value);
|
||||
|
||||
failed = failed ||
|
||||
self.clear_inputs.iter().any(|input| {
|
||||
pedersen_commitment_base(input.token_id.inner(), input.token_blind) !=
|
||||
token_commit_value
|
||||
});
|
||||
!failed
|
||||
}
|
||||
}
|
||||
|
||||
/// A transaction's clear input
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct ClearInput {
|
||||
/// Input's value (amount)
|
||||
pub value: u64,
|
||||
/// Input's token ID
|
||||
pub token_id: TokenId,
|
||||
/// Blinding factor for `value`
|
||||
pub value_blind: DrkValueBlind,
|
||||
/// Blinding factor for `token_id`
|
||||
pub token_blind: DrkValueBlind,
|
||||
/// Public key for the signature
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous input
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
/// Public inputs for the zero-knowledge proof
|
||||
pub revealed: BurnRevealedValues,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous output
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct Output {
|
||||
/// Public inputs for the zero-knowledge proof
|
||||
pub revealed: MintRevealedValues,
|
||||
/// The encrypted note
|
||||
pub enc_note: EncryptedNote2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
VerifyFailed(#[from] VerifyFailed),
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
|
||||
/// Transaction verification errors
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum VerifyFailed {
|
||||
#[error("Transaction has no inputs")]
|
||||
LackingInputs,
|
||||
|
||||
#[error("Transaction has no outputs")]
|
||||
LackingOutputs,
|
||||
|
||||
#[error("Invalid cashier/faucet public key for clear input {0}")]
|
||||
InvalidCashierOrFaucetKey(usize),
|
||||
|
||||
#[error("Invalid Merkle root for input {0}")]
|
||||
InvalidMerkle(usize),
|
||||
|
||||
#[error("Spend hook invoking function is not attached")]
|
||||
SpendHookNotSatisfied,
|
||||
|
||||
#[error("Nullifier already exists for input {0}")]
|
||||
NullifierExists(usize),
|
||||
|
||||
#[error("Token commitments in inputs or outputs to not match")]
|
||||
TokenMismatch,
|
||||
|
||||
#[error("Money in does not match money out (value commitments)")]
|
||||
MissingFunds,
|
||||
|
||||
#[error("Failed verifying zk proofs: {0}")]
|
||||
ProofVerifyFailed(String),
|
||||
|
||||
#[error("Internal error: {0}")]
|
||||
InternalError(String),
|
||||
|
||||
#[error("DarkFi error: {0}")]
|
||||
DarkFiError(String),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl From<Error> for VerifyFailed {
|
||||
fn from(err: Error) -> Self {
|
||||
Self::InternalError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DarkFiError> for VerifyFailed {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DarkFiError> for Error {
|
||||
fn from(err: DarkFiError) -> Self {
|
||||
Self::DarkFiError(err.to_string())
|
||||
}
|
||||
}
|
||||
/// Result type used in transaction verifications
|
||||
pub type VerifyResult<T> = std::result::Result<T, VerifyFailed>;
|
||||
@@ -1,241 +0,0 @@
|
||||
/* 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::{MerkleNode, PublicKey, SecretKey, TokenId};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
use pasta_curves::group::ff::Field;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
burn_proof::create_burn_proof,
|
||||
mint_proof::create_mint_proof,
|
||||
types::{
|
||||
DrkCoinBlind, DrkSerial, DrkSpendHook, DrkUserData, DrkUserDataBlind, DrkValueBlind,
|
||||
},
|
||||
Proof,
|
||||
},
|
||||
Result,
|
||||
};
|
||||
|
||||
use money_contract::{ClearInput, Input, MoneyFunction, MoneyTransferParams, Output};
|
||||
|
||||
use crate::{
|
||||
note,
|
||||
util::{ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct Note {
|
||||
pub serial: DrkSerial,
|
||||
pub value: u64,
|
||||
pub token_id: TokenId,
|
||||
pub spend_hook: DrkSpendHook,
|
||||
pub user_data: DrkUserData,
|
||||
pub coin_blind: DrkCoinBlind,
|
||||
pub value_blind: DrkValueBlind,
|
||||
pub token_blind: DrkValueBlind,
|
||||
}
|
||||
|
||||
pub struct Builder {
|
||||
pub clear_inputs: Vec<BuilderClearInputInfo>,
|
||||
pub inputs: Vec<BuilderInputInfo>,
|
||||
pub outputs: Vec<BuilderOutputInfo>,
|
||||
}
|
||||
|
||||
pub struct BuilderClearInputInfo {
|
||||
pub value: u64,
|
||||
pub token_id: TokenId,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
pub struct BuilderInputInfo {
|
||||
pub leaf_position: incrementalmerkletree::Position,
|
||||
pub merkle_path: Vec<MerkleNode>,
|
||||
pub secret: SecretKey,
|
||||
pub note: Note,
|
||||
pub user_data_blind: DrkUserDataBlind,
|
||||
pub value_blind: DrkValueBlind,
|
||||
pub signature_secret: SecretKey,
|
||||
}
|
||||
|
||||
pub struct BuilderOutputInfo {
|
||||
pub value: u64,
|
||||
pub token_id: TokenId,
|
||||
pub public: PublicKey,
|
||||
pub serial: DrkSerial,
|
||||
pub coin_blind: DrkCoinBlind,
|
||||
pub spend_hook: DrkSpendHook,
|
||||
pub user_data: DrkUserData,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
fn compute_remainder_blind(
|
||||
clear_inputs: &[ClearInput],
|
||||
input_blinds: &[DrkValueBlind],
|
||||
output_blinds: &[DrkValueBlind],
|
||||
) -> DrkValueBlind {
|
||||
let mut total = DrkValueBlind::zero();
|
||||
|
||||
for input in clear_inputs {
|
||||
total += input.value_blind;
|
||||
}
|
||||
|
||||
for input_blind in input_blinds {
|
||||
total += input_blind;
|
||||
}
|
||||
|
||||
for output_blind in output_blinds {
|
||||
total -= output_blind;
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn build(self, zk_bins: &ZkContractTable) -> Result<(MoneyTransferParams, Vec<Proof>)> {
|
||||
assert!(self.clear_inputs.len() + self.inputs.len() > 0);
|
||||
|
||||
let mut clear_inputs = vec![];
|
||||
let token_blind = DrkValueBlind::random(&mut OsRng);
|
||||
for input in &self.clear_inputs {
|
||||
let signature_public = PublicKey::from_secret(input.signature_secret);
|
||||
let value_blind = DrkValueBlind::random(&mut OsRng);
|
||||
|
||||
let clear_input = ClearInput {
|
||||
value: input.value,
|
||||
token_id: input.token_id.inner(),
|
||||
value_blind,
|
||||
token_blind,
|
||||
signature_public,
|
||||
};
|
||||
clear_inputs.push(clear_input);
|
||||
}
|
||||
|
||||
let mut proofs = vec![];
|
||||
let mut inputs = vec![];
|
||||
let mut input_blinds = vec![];
|
||||
|
||||
for input in self.inputs {
|
||||
let value_blind = input.value_blind;
|
||||
input_blinds.push(value_blind);
|
||||
|
||||
let zk_info = zk_bins.lookup(&"money-transfer-burn".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Native(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not native info")
|
||||
};
|
||||
let burn_pk = &zk_info.proving_key;
|
||||
|
||||
// Note from the previous output
|
||||
let note = input.note.clone();
|
||||
|
||||
let (burn_proof, revealed) = create_burn_proof(
|
||||
burn_pk,
|
||||
note.value,
|
||||
note.token_id,
|
||||
value_blind,
|
||||
token_blind,
|
||||
note.serial,
|
||||
note.spend_hook,
|
||||
note.user_data,
|
||||
input.user_data_blind,
|
||||
note.coin_blind,
|
||||
input.secret,
|
||||
input.leaf_position,
|
||||
input.merkle_path.clone(),
|
||||
input.signature_secret,
|
||||
)?;
|
||||
proofs.push(burn_proof);
|
||||
|
||||
let input = Input {
|
||||
value_commit: revealed.value_commit,
|
||||
token_commit: revealed.token_commit,
|
||||
nullifier: revealed.nullifier.inner(),
|
||||
merkle_root: revealed.merkle_root.inner(),
|
||||
spend_hook: revealed.spend_hook,
|
||||
user_data_enc: revealed.user_data_enc,
|
||||
signature_public: revealed.signature_public,
|
||||
};
|
||||
inputs.push(input);
|
||||
}
|
||||
|
||||
let mut outputs = vec![];
|
||||
let mut output_blinds = vec![];
|
||||
// This value_blind calc assumes there will always be at least a single output
|
||||
assert!(self.outputs.len() > 0);
|
||||
|
||||
for (i, output) in self.outputs.iter().enumerate() {
|
||||
let value_blind = if i == self.outputs.len() - 1 {
|
||||
Self::compute_remainder_blind(&clear_inputs, &input_blinds, &output_blinds)
|
||||
} else {
|
||||
DrkValueBlind::random(&mut OsRng)
|
||||
};
|
||||
output_blinds.push(value_blind);
|
||||
|
||||
let serial = output.serial;
|
||||
let coin_blind = output.coin_blind;
|
||||
|
||||
let zk_info = zk_bins.lookup(&"money-transfer-mint".to_string()).unwrap();
|
||||
let zk_info = if let ZkContractInfo::Native(info) = zk_info {
|
||||
info
|
||||
} else {
|
||||
panic!("Not native info")
|
||||
};
|
||||
let mint_pk = &zk_info.proving_key;
|
||||
|
||||
let (mint_proof, revealed) = create_mint_proof(
|
||||
mint_pk,
|
||||
output.value,
|
||||
output.token_id,
|
||||
value_blind,
|
||||
token_blind,
|
||||
serial,
|
||||
output.spend_hook,
|
||||
output.user_data,
|
||||
coin_blind,
|
||||
output.public,
|
||||
)?;
|
||||
proofs.push(mint_proof);
|
||||
|
||||
let note = Note {
|
||||
serial,
|
||||
value: output.value,
|
||||
token_id: output.token_id,
|
||||
spend_hook: output.spend_hook,
|
||||
user_data: output.user_data,
|
||||
coin_blind,
|
||||
value_blind,
|
||||
token_blind,
|
||||
};
|
||||
|
||||
let encrypted_note = note::encrypt(¬e, &output.public)?;
|
||||
|
||||
let output = Output {
|
||||
value_commit: revealed.value_commit,
|
||||
token_commit: revealed.token_commit,
|
||||
coin: revealed.coin.0,
|
||||
ciphertext: encrypted_note.ciphertext,
|
||||
ephem_public: encrypted_note.ephem_public,
|
||||
};
|
||||
outputs.push(output);
|
||||
}
|
||||
|
||||
Ok((MoneyTransferParams { clear_inputs, inputs, outputs }, proofs))
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/* 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 serde_json::Value;
|
||||
|
||||
// use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DaoError {
|
||||
// #[error("No Proposals found")]
|
||||
// NoProposals,
|
||||
// #[error("No DAO params found")]
|
||||
// DaoNotConfigured,
|
||||
// #[error("State transition failed: '{0}'")]
|
||||
// StateTransitionFailed(String),
|
||||
// #[error("Wallet does not exist")]
|
||||
// NoWalletFound,
|
||||
// #[error("State not found")]
|
||||
// StateNotFound,
|
||||
#[error("InternalError")]
|
||||
Darkfi(#[from] darkfi::error::Error),
|
||||
#[error("Verify proof failed: '{0}', '{0}'")]
|
||||
VerifyProofFailed(usize, String),
|
||||
}
|
||||
|
||||
pub type DaoResult<T> = std::result::Result<T, DaoError>;
|
||||
|
||||
// pub enum RpcError {
|
||||
// Vote = -32101,
|
||||
// Propose = -32102,
|
||||
// Exec = -32103,
|
||||
// Airdrop = -32104,
|
||||
// Mint = -32105,
|
||||
// Keygen = -32106,
|
||||
// Create = -32107,
|
||||
// Parse = -32108,
|
||||
// Balance = -32109,
|
||||
// }
|
||||
|
||||
// fn to_tuple(e: RpcError) -> (i64, String) {
|
||||
// let msg = match e {
|
||||
// RpcError::Vote => "Failed to cast a Vote",
|
||||
// RpcError::Propose => "Failed to generate a Proposal",
|
||||
// RpcError::Airdrop => "Failed to transfer an airdrop",
|
||||
// RpcError::Keygen => "Failed to generate keypair",
|
||||
// RpcError::Create => "Failed to create DAO",
|
||||
// RpcError::Exec => "Failed to execute Proposal",
|
||||
// RpcError::Mint => "Failed to mint DAO treasury",
|
||||
// RpcError::Parse => "Generic parsing error",
|
||||
// RpcError::Balance => "Failed to get balance",
|
||||
// };
|
||||
|
||||
// (e as i64, msg.to_string())
|
||||
// }
|
||||
|
||||
// pub fn server_error(e: RpcError, id: Value) -> JsonResult {
|
||||
// let (code, msg) = to_tuple(e);
|
||||
// JsonError::new(ServerError(code), Some(msg), id).into()
|
||||
// }
|
||||
@@ -1,540 +0,0 @@
|
||||
/* 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::{
|
||||
blockchain::Blockchain,
|
||||
consensus::{TESTNET_GENESIS_HASH_BYTES, TESTNET_GENESIS_TIMESTAMP},
|
||||
crypto::{
|
||||
coin::Coin,
|
||||
proof::{ProvingKey, VerifyingKey},
|
||||
types::{DrkSpendHook, DrkUserData, DrkValue},
|
||||
},
|
||||
runtime::vm_runtime::Runtime,
|
||||
zk::circuit::{BurnContract, MintContract},
|
||||
zkas::decoder::ZkBinary,
|
||||
Result,
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
constants::MERKLE_DEPTH, pedersen::pedersen_commitment_u64, poseidon_hash,
|
||||
schnorr::SchnorrSecret, ContractId, Keypair, MerkleNode, MerkleTree, PublicKey, SecretKey,
|
||||
TokenId,
|
||||
},
|
||||
tx::ContractCall,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Decodable, Encodable, WriteExt};
|
||||
use incrementalmerkletree::{bridgetree::BridgeTree, Tree};
|
||||
use log::{debug, error};
|
||||
use pasta_curves::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{ff::Field, Curve},
|
||||
pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
io::Cursor,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use dao_contract::{DaoFunction, DaoMintParams};
|
||||
use money_contract::{MoneyFunction, MoneyTransferParams};
|
||||
|
||||
use crate::{
|
||||
contract::{dao, example, money},
|
||||
note::EncryptedNote2,
|
||||
schema::WalletCache,
|
||||
tx::Transaction,
|
||||
util::{StateRegistry, ZkContractTable},
|
||||
};
|
||||
|
||||
mod contract;
|
||||
mod error;
|
||||
mod note;
|
||||
mod schema;
|
||||
mod tx;
|
||||
mod util;
|
||||
|
||||
fn show_dao_state(chain: &Blockchain, contract_id: &ContractId) -> Result<()> {
|
||||
let db_info = chain.contracts.lookup(&chain.sled_db, contract_id, "info")?;
|
||||
let value = db_info.get(&serialize(&"dao_tree".to_string())).expect("dao_tree").unwrap();
|
||||
let mut decoder = Cursor::new(&value);
|
||||
let set_size: u32 = Decodable::decode(&mut decoder)?;
|
||||
let tree: MerkleTree = Decodable::decode(decoder)?;
|
||||
debug!(target: "demo", "DAO state:");
|
||||
debug!(target: "demo", " tree: {} bytes", value.len());
|
||||
debug!(target: "demo", " set size: {}", set_size);
|
||||
|
||||
let db_roots = chain.contracts.lookup(&chain.sled_db, contract_id, "dao_roots")?;
|
||||
for i in 0..set_size {
|
||||
let root = db_roots.get(&serialize(&i)).expect("dao_roots").unwrap();
|
||||
let root: MerkleNode = deserialize(&root)?;
|
||||
debug!(target: "demo", " root {}: {:?}", i, root);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_money_state(chain: &Blockchain, contract_id: &ContractId) -> Result<()> {
|
||||
let db_info = chain.contracts.lookup(&chain.sled_db, contract_id, "info")?;
|
||||
let value = db_info.get(&serialize(&"coin_tree".to_string())).expect("coin_tree").unwrap();
|
||||
let mut decoder = Cursor::new(&value);
|
||||
let set_size: u32 = Decodable::decode(&mut decoder)?;
|
||||
let tree: MerkleTree = Decodable::decode(decoder)?;
|
||||
debug!(target: "demo", "Money state:");
|
||||
debug!(target: "demo", " tree: {} bytes", value.len());
|
||||
debug!(target: "demo", " set size: {}", set_size);
|
||||
|
||||
let db_roots = chain.contracts.lookup(&chain.sled_db, contract_id, "coin_roots")?;
|
||||
for i in 0..set_size {
|
||||
let root = db_roots.get(&serialize(&i)).expect("coin_roots").unwrap();
|
||||
let root: MerkleNode = deserialize(&root)?;
|
||||
debug!(target: "demo", " root {}: {:?}", i, root);
|
||||
}
|
||||
|
||||
let db_nulls = chain.contracts.lookup(&chain.sled_db, contract_id, "info")?;
|
||||
debug!(target: "demo", " nullifiers:");
|
||||
for obj in db_nulls.iter() {
|
||||
let (key, value) = obj.unwrap();
|
||||
debug!(target: "demo", " {:02x?}", &key[..]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type BoxResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
fn validate(
|
||||
tx: &Transaction,
|
||||
dao_wasm_bytes: &[u8],
|
||||
dao_contract_id: ContractId,
|
||||
money_wasm_bytes: &[u8],
|
||||
money_contract_id: ContractId,
|
||||
blockchain: &Blockchain,
|
||||
zk_bins: &ZkContractTable,
|
||||
) -> Result<()> {
|
||||
// ContractId is not Hashable so put them in a Vec and do linear scan
|
||||
let wasm_bytes_lookup = vec![
|
||||
(dao_contract_id, "DAO", dao_wasm_bytes),
|
||||
(money_contract_id, "Money", money_wasm_bytes),
|
||||
];
|
||||
|
||||
// We can do all exec(), zk proof checks and signature verifies in parallel.
|
||||
let mut updates = vec![];
|
||||
let mut zkpublic_table = vec![];
|
||||
let mut sigpub_table = vec![];
|
||||
// Validate all function calls in the tx
|
||||
for (idx, call) in tx.calls.iter().enumerate() {
|
||||
// So then the verifier will lookup the corresponding state_transition and apply
|
||||
// functions based off the func_id
|
||||
|
||||
// Write the actual payload data
|
||||
let mut payload = Vec::new();
|
||||
// Call index
|
||||
payload.write_u32(idx as u32)?;
|
||||
// Actuall calldata
|
||||
tx.calls.encode(&mut payload)?;
|
||||
|
||||
// Lookup the wasm bytes
|
||||
let (_, contract_name, wasm_bytes) =
|
||||
wasm_bytes_lookup.iter().find(|(id, _name, _bytes)| *id == call.contract_id).unwrap();
|
||||
debug!(target: "demo", "{}::exec() contract called", contract_name);
|
||||
|
||||
let mut runtime = Runtime::new(wasm_bytes, blockchain.clone(), call.contract_id)?;
|
||||
let update = runtime.exec(&payload)?;
|
||||
updates.push(update);
|
||||
|
||||
let metadata = runtime.metadata(&payload)?;
|
||||
let mut decoder = Cursor::new(&metadata);
|
||||
let zk_public_values: Vec<(String, Vec<pallas::Base>)> = Decodable::decode(&mut decoder)?;
|
||||
let signature_public_keys: Vec<pallas::Point> = Decodable::decode(&mut decoder)?;
|
||||
|
||||
zkpublic_table.push(zk_public_values);
|
||||
sigpub_table.push(signature_public_keys);
|
||||
}
|
||||
|
||||
tx.zk_verify(&zk_bins, &zkpublic_table)?;
|
||||
tx.verify_sigs(&sigpub_table)?;
|
||||
|
||||
// Now we finished verification stage, just apply all changes
|
||||
assert_eq!(tx.calls.len(), updates.len());
|
||||
for (call, update) in tx.calls.iter().zip(updates.iter()) {
|
||||
// Lookup the wasm bytes
|
||||
let (_, contract_name, wasm_bytes) =
|
||||
wasm_bytes_lookup.iter().find(|(id, _name, _bytes)| *id == call.contract_id).unwrap();
|
||||
debug!(target: "demo", "{}::apply() contract called", contract_name);
|
||||
|
||||
let mut runtime = Runtime::new(wasm_bytes, blockchain.clone(), call.contract_id)?;
|
||||
|
||||
runtime.apply(&update)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> BoxResult<()> {
|
||||
// Debug log configuration
|
||||
let mut cfg = simplelog::ConfigBuilder::new();
|
||||
cfg.add_filter_ignore("sled".to_string());
|
||||
simplelog::TermLogger::init(
|
||||
simplelog::LevelFilter::Debug,
|
||||
cfg.build(),
|
||||
simplelog::TerminalMode::Mixed,
|
||||
simplelog::ColorChoice::Auto,
|
||||
)?;
|
||||
|
||||
println!("wakie wakie young wagie");
|
||||
|
||||
//return Ok(());
|
||||
|
||||
//schema::schema().await?;
|
||||
//return Ok(());
|
||||
|
||||
// =============================
|
||||
// Setup initial program parameters
|
||||
// =============================
|
||||
|
||||
// Money parameters
|
||||
let xdrk_supply = 1_000_000;
|
||||
let xdrk_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
|
||||
|
||||
// Governance token parameters
|
||||
let gdrk_supply = 1_000_000;
|
||||
let gdrk_token_id = TokenId::from(pallas::Base::random(&mut OsRng));
|
||||
|
||||
// DAO parameters
|
||||
let dao_proposer_limit = 110;
|
||||
let dao_quorum = 110;
|
||||
let dao_approval_ratio_quot = 1;
|
||||
let dao_approval_ratio_base = 2;
|
||||
|
||||
// Initialize ZK binary table
|
||||
let mut zk_bins = ZkContractTable::new();
|
||||
|
||||
debug!(target: "demo", "Loading dao-mint.zk");
|
||||
let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin");
|
||||
let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?;
|
||||
zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13);
|
||||
|
||||
debug!(target: "demo", "Loading money-transfer contracts");
|
||||
{
|
||||
let start = Instant::now();
|
||||
let mint_pk = ProvingKey::build(11, &MintContract::default());
|
||||
debug!("Mint PK: [{:?}]", start.elapsed());
|
||||
let start = Instant::now();
|
||||
let burn_pk = ProvingKey::build(11, &BurnContract::default());
|
||||
debug!("Burn PK: [{:?}]", start.elapsed());
|
||||
let start = Instant::now();
|
||||
let mint_vk = VerifyingKey::build(11, &MintContract::default());
|
||||
debug!("Mint VK: [{:?}]", start.elapsed());
|
||||
let start = Instant::now();
|
||||
let burn_vk = VerifyingKey::build(11, &BurnContract::default());
|
||||
debug!("Burn VK: [{:?}]", start.elapsed());
|
||||
|
||||
zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk);
|
||||
zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk);
|
||||
}
|
||||
/*
|
||||
debug!(target: "demo", "Loading dao-propose-main.zk");
|
||||
let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin");
|
||||
let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?;
|
||||
zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13);
|
||||
debug!(target: "demo", "Loading dao-propose-burn.zk");
|
||||
let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin");
|
||||
let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?;
|
||||
zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13);
|
||||
debug!(target: "demo", "Loading dao-vote-main.zk");
|
||||
let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin");
|
||||
let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?;
|
||||
zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13);
|
||||
debug!(target: "demo", "Loading dao-vote-burn.zk");
|
||||
let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin");
|
||||
let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?;
|
||||
zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13);
|
||||
let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin");
|
||||
let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?;
|
||||
zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13);
|
||||
*/
|
||||
|
||||
// State for money contracts
|
||||
let cashier_signature_secret = SecretKey::random(&mut OsRng);
|
||||
let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret);
|
||||
let faucet_signature_secret = SecretKey::random(&mut OsRng);
|
||||
let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret);
|
||||
|
||||
// We use this to receive coins
|
||||
let mut cache = WalletCache::new();
|
||||
|
||||
// Initialize a dummy blockchain
|
||||
// TODO: This blockchain interface should perhaps be ValidatorState and Mutex/RwLock.
|
||||
let db = sled::Config::new().temporary(true).open()?;
|
||||
let blockchain = Blockchain::new(&db, *TESTNET_GENESIS_TIMESTAMP, *TESTNET_GENESIS_HASH_BYTES)?;
|
||||
|
||||
// ================================================================
|
||||
// Deploy the wasm contracts
|
||||
// ================================================================
|
||||
|
||||
let dao_wasm_bytes = std::fs::read("dao_contract.wasm")?;
|
||||
let dao_contract_id = ContractId::from(pallas::Base::from(1));
|
||||
let money_wasm_bytes = std::fs::read("money_contract.wasm")?;
|
||||
let money_contract_id = ContractId::from(pallas::Base::from(2));
|
||||
|
||||
// Block 1
|
||||
// This has 2 transaction deploying the DAO and Money wasm contracts
|
||||
// together with their ZK proofs.
|
||||
{
|
||||
let mut dao_runtime = Runtime::new(&dao_wasm_bytes, blockchain.clone(), dao_contract_id)?;
|
||||
let mut money_runtime =
|
||||
Runtime::new(&money_wasm_bytes, blockchain.clone(), money_contract_id)?;
|
||||
|
||||
// 1. exec() - zk and sig verify also
|
||||
// ... none in this block
|
||||
|
||||
// 2. commit() - all apply() and deploy()
|
||||
// Deploy function to initialize the smart contract state.
|
||||
// Here we pass an empty payload, but it's possible to feed in arbitrary data.
|
||||
dao_runtime.deploy(&[])?;
|
||||
money_runtime.deploy(&[])?;
|
||||
debug!(target: "demo", "Deployed DAO and money contracts");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// DAO::mint()
|
||||
// ================================================================
|
||||
|
||||
// Wallet
|
||||
let dao_keypair = Keypair::random(&mut OsRng);
|
||||
let dao_bulla_blind = pallas::Base::random(&mut OsRng);
|
||||
let tx = {
|
||||
let signature_secret = SecretKey::random(&mut OsRng);
|
||||
// Create DAO mint tx
|
||||
let builder = dao::mint::wallet::Builder {
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id: gdrk_token_id,
|
||||
dao_pubkey: dao_keypair.public,
|
||||
dao_bulla_blind,
|
||||
signature_secret,
|
||||
};
|
||||
let (params, dao_mint_proofs) = builder.build(&zk_bins);
|
||||
|
||||
// Write the actual call data
|
||||
let mut calldata = Vec::new();
|
||||
// Selects which path executes in the contract.
|
||||
calldata.write_u8(DaoFunction::Mint as u8)?;
|
||||
params.encode(&mut calldata)?;
|
||||
|
||||
let calls = vec![ContractCall { contract_id: dao_contract_id, data: calldata }];
|
||||
|
||||
let signatures = vec![];
|
||||
//for func_call in &func_calls {
|
||||
// let sign = sign([signature_secret].to_vec(), func_call);
|
||||
// signatures.push(sign);
|
||||
//}
|
||||
|
||||
let proofs = vec![dao_mint_proofs];
|
||||
|
||||
Transaction { calls, proofs, signatures }
|
||||
};
|
||||
|
||||
//// Validator
|
||||
|
||||
validate(
|
||||
&tx,
|
||||
&dao_wasm_bytes,
|
||||
dao_contract_id,
|
||||
&money_wasm_bytes,
|
||||
money_contract_id,
|
||||
&blockchain,
|
||||
&zk_bins,
|
||||
)
|
||||
.expect("validate failed");
|
||||
|
||||
// Wallet stuff
|
||||
|
||||
// In your wallet, wait until you see the tx confirmed before doing anything below
|
||||
// So for example keep track of tx hash
|
||||
//
|
||||
// We also need to loop through all newly added items to the validator node
|
||||
// and repeat the same for our local merkle tree. The order of added items
|
||||
// to local merkle trees must be the same.
|
||||
//
|
||||
// One way to do this would be that .apply() keeps an in-memory per block
|
||||
// list of the order txs were applied. So then we can repeat the same order
|
||||
// for our local wallet trees.
|
||||
//
|
||||
// [ tx1, tx2, ... ]
|
||||
//
|
||||
// So the wallets know these are the new txs and this was the order they
|
||||
// were applied to the state in.
|
||||
// State updates are atomic so this will always be linear.
|
||||
//
|
||||
// When we see our DAO bulla, we call .witness()
|
||||
|
||||
// We need to witness() the value in our local merkle tree
|
||||
let dao_bulla = {
|
||||
assert_eq!(tx.calls.len(), 1);
|
||||
let calldata = &tx.calls[0].data;
|
||||
let params_data = &calldata[1..];
|
||||
let params: DaoMintParams = Decodable::decode(params_data)?;
|
||||
params.dao_bulla.clone()
|
||||
};
|
||||
|
||||
let mut dao_tree = MerkleTree::new(100);
|
||||
let dao_leaf_position = {
|
||||
let node = MerkleNode::from(dao_bulla.0);
|
||||
dao_tree.append(&node);
|
||||
dao_tree.witness().unwrap()
|
||||
};
|
||||
|
||||
debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0);
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
//// Mint the initial supply of treasury token
|
||||
//// and send it all to the DAO directly
|
||||
///////////////////////////////////////////////////
|
||||
debug!(target: "demo", "Stage 2. Minting treasury token");
|
||||
|
||||
cache.track(dao_keypair.secret);
|
||||
|
||||
//// Wallet
|
||||
|
||||
// Address of deployed contract in our example is dao::exec::FUNC_ID
|
||||
// This field is public, you can see it's being sent to a DAO
|
||||
// but nothing else is visible.
|
||||
//
|
||||
// In the python code we wrote:
|
||||
//
|
||||
// spend_hook = b"0xdao_ruleset"
|
||||
//
|
||||
let spend_hook = *dao::exec::FUNC_ID;
|
||||
let tx = {
|
||||
// The user_data can be a simple hash of the items passed into the ZK proof
|
||||
// up to corresponding linked ZK proof to interpret however they need.
|
||||
// In out case, it's the bulla for the DAO
|
||||
let user_data = dao_bulla.0;
|
||||
|
||||
let builder = money::transfer::wallet::Builder {
|
||||
clear_inputs: vec![money::transfer::wallet::BuilderClearInputInfo {
|
||||
value: xdrk_supply,
|
||||
token_id: xdrk_token_id,
|
||||
signature_secret: cashier_signature_secret,
|
||||
}],
|
||||
inputs: vec![],
|
||||
outputs: vec![money::transfer::wallet::BuilderOutputInfo {
|
||||
value: xdrk_supply,
|
||||
token_id: xdrk_token_id,
|
||||
public: dao_keypair.public,
|
||||
serial: pallas::Base::random(&mut OsRng),
|
||||
coin_blind: pallas::Base::random(&mut OsRng),
|
||||
spend_hook,
|
||||
user_data,
|
||||
}],
|
||||
};
|
||||
let (params, proofs) = builder.build(&zk_bins)?;
|
||||
|
||||
// Write the actual call data
|
||||
let mut calldata = Vec::new();
|
||||
// Selects which path executes in the contract.
|
||||
calldata.write_u8(MoneyFunction::Transfer as u8)?;
|
||||
params.encode(&mut calldata)?;
|
||||
|
||||
let calls = vec![ContractCall { contract_id: money_contract_id, data: calldata }];
|
||||
|
||||
let proofs = vec![proofs];
|
||||
|
||||
// We sign everything
|
||||
let mut unsigned_tx_data = vec![];
|
||||
calls.encode(&mut unsigned_tx_data)?;
|
||||
proofs.encode(&mut unsigned_tx_data)?;
|
||||
let signature = cashier_signature_secret.sign(&mut OsRng, &unsigned_tx_data[..]);
|
||||
|
||||
// Our tx has a single contract call which itself has a single input
|
||||
let signatures = vec![vec![signature]];
|
||||
|
||||
Transaction { calls, proofs, signatures }
|
||||
};
|
||||
|
||||
//// Validator
|
||||
|
||||
validate(
|
||||
&tx,
|
||||
&dao_wasm_bytes,
|
||||
dao_contract_id,
|
||||
&money_wasm_bytes,
|
||||
money_contract_id,
|
||||
&blockchain,
|
||||
&zk_bins,
|
||||
)
|
||||
.expect("validate failed");
|
||||
|
||||
// Wallet stuff
|
||||
|
||||
// DAO reads the money received from the encrypted note
|
||||
{
|
||||
assert_eq!(tx.calls.len(), 1);
|
||||
let calldata = &tx.calls[0].data;
|
||||
let params_data = &calldata[1..];
|
||||
let params: MoneyTransferParams = Decodable::decode(params_data)?;
|
||||
|
||||
for output in params.outputs {
|
||||
let coin = output.coin;
|
||||
let enc_note = note::EncryptedNote2 {
|
||||
ciphertext: output.ciphertext,
|
||||
ephem_public: output.ephem_public,
|
||||
};
|
||||
|
||||
let coin = Coin(coin);
|
||||
cache.try_decrypt_note(coin, &enc_note);
|
||||
}
|
||||
}
|
||||
|
||||
let mut recv_coins = cache.get_received(&dao_keypair.secret);
|
||||
assert_eq!(recv_coins.len(), 1);
|
||||
let dao_recv_coin = recv_coins.pop().unwrap();
|
||||
let treasury_note = dao_recv_coin.note;
|
||||
|
||||
// Check the actual coin received is valid before accepting it
|
||||
|
||||
let coords = dao_keypair.public.inner().to_affine().coordinates().unwrap();
|
||||
let coin = poseidon_hash::<8>([
|
||||
*coords.x(),
|
||||
*coords.y(),
|
||||
DrkValue::from(treasury_note.value),
|
||||
treasury_note.token_id.inner(),
|
||||
treasury_note.serial,
|
||||
treasury_note.spend_hook,
|
||||
treasury_note.user_data,
|
||||
treasury_note.coin_blind,
|
||||
]);
|
||||
assert_eq!(coin, dao_recv_coin.coin.0);
|
||||
|
||||
assert_eq!(treasury_note.spend_hook, *dao::exec::FUNC_ID);
|
||||
assert_eq!(treasury_note.user_data, dao_bulla.0);
|
||||
|
||||
debug!("DAO received a coin worth {} xDRK", treasury_note.value);
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
|
||||
show_dao_state(&blockchain, &dao_contract_id)?;
|
||||
show_money_state(&blockchain, &money_contract_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/* 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 chacha20poly1305::{AeadInPlace, ChaCha20Poly1305, KeyInit};
|
||||
use darkfi_sdk::crypto::{PublicKey, SecretKey};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::diffie_hellman::{kdf_sapling, sapling_ka_agree},
|
||||
Error, Result,
|
||||
};
|
||||
use darkfi_serial::{Decodable, Encodable, SerialDecodable, SerialEncodable};
|
||||
|
||||
pub const AEAD_TAG_SIZE: usize = 16;
|
||||
|
||||
pub fn encrypt<T: Encodable>(note: &T, public: &PublicKey) -> Result<EncryptedNote2> {
|
||||
let ephem_secret = SecretKey::random(&mut OsRng);
|
||||
let ephem_public = PublicKey::from_secret(ephem_secret);
|
||||
let shared_secret = sapling_ka_agree(&ephem_secret, public);
|
||||
let key = kdf_sapling(&shared_secret, &ephem_public);
|
||||
|
||||
let mut input = Vec::new();
|
||||
note.encode(&mut input)?;
|
||||
let input_len = input.len();
|
||||
|
||||
let mut ciphertext = vec![0_u8; input_len + AEAD_TAG_SIZE];
|
||||
ciphertext[..input_len].copy_from_slice(&input);
|
||||
|
||||
ChaCha20Poly1305::new(key.as_ref().into())
|
||||
.encrypt_in_place([0u8; 12][..].into(), &[], &mut ciphertext)
|
||||
.unwrap();
|
||||
|
||||
Ok(EncryptedNote2 { ciphertext, ephem_public })
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)]
|
||||
pub struct EncryptedNote2 {
|
||||
pub ciphertext: Vec<u8>,
|
||||
pub ephem_public: PublicKey,
|
||||
}
|
||||
|
||||
impl EncryptedNote2 {
|
||||
pub fn decrypt<T: Decodable>(&self, secret: &SecretKey) -> Result<T> {
|
||||
let shared_secret = sapling_ka_agree(secret, &self.ephem_public);
|
||||
let key = kdf_sapling(&shared_secret, &self.ephem_public);
|
||||
|
||||
let ciphertext_len = self.ciphertext.len();
|
||||
let mut plaintext = vec![0_u8; ciphertext_len];
|
||||
plaintext.copy_from_slice(&self.ciphertext);
|
||||
|
||||
match ChaCha20Poly1305::new(key.as_ref().into()).decrypt_in_place(
|
||||
[0u8; 12][..].into(),
|
||||
&[],
|
||||
&mut plaintext,
|
||||
) {
|
||||
Ok(()) => {
|
||||
Ok(T::decode(&plaintext[..ciphertext_len - AEAD_TAG_SIZE]).map_err(Error::from)?)
|
||||
}
|
||||
Err(e) => Err(Error::NoteDecryptionFailed(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use darkfi::crypto::{
|
||||
keypair::Keypair,
|
||||
types::{DrkCoinBlind, DrkSerial, DrkValueBlind},
|
||||
};
|
||||
use darkfi_sdk::{
|
||||
crypto::TokenId,
|
||||
pasta::{group::ff::Field, pallas},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_note_encdec() {
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
struct MyNote {
|
||||
serial: DrkSerial,
|
||||
value: u64,
|
||||
token_id: TokenId,
|
||||
coin_blind: DrkCoinBlind,
|
||||
value_blind: DrkValueBlind,
|
||||
token_blind: DrkValueBlind,
|
||||
memo: Vec<u8>,
|
||||
}
|
||||
let note = MyNote {
|
||||
serial: DrkSerial::random(&mut OsRng),
|
||||
value: 110,
|
||||
token_id: TokenId::from(pallas::Base::random(&mut OsRng)),
|
||||
coin_blind: DrkCoinBlind::random(&mut OsRng),
|
||||
value_blind: DrkValueBlind::random(&mut OsRng),
|
||||
token_blind: DrkValueBlind::random(&mut OsRng),
|
||||
memo: vec![32, 223, 231, 3, 1, 1],
|
||||
};
|
||||
|
||||
let keypair = Keypair::random(&mut OsRng);
|
||||
|
||||
let encrypted_note = encrypt(¬e, &keypair.public).unwrap();
|
||||
let note2: MyNote = encrypted_note.decrypt(&keypair.secret).unwrap();
|
||||
assert_eq!(note.value, note2.value);
|
||||
assert_eq!(note.token_id, note2.token_id);
|
||||
assert_eq!(note.token_blind, note2.token_blind);
|
||||
assert_eq!(note.memo, note2.memo);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,128 +0,0 @@
|
||||
/* 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::{crypto::Proof, Result, VerifyFailed::ProofVerifyFailed};
|
||||
use darkfi_sdk::{
|
||||
crypto::{
|
||||
schnorr::{SchnorrPublic, Signature},
|
||||
PublicKey,
|
||||
},
|
||||
pasta::pallas,
|
||||
tx::ContractCall,
|
||||
};
|
||||
use darkfi_serial::Encodable;
|
||||
use log::debug;
|
||||
|
||||
use crate::{
|
||||
contract::{dao, example, money},
|
||||
note::EncryptedNote2,
|
||||
schema::WalletCache,
|
||||
util::{sign, StateRegistry, ZkContractInfo, ZkContractTable},
|
||||
};
|
||||
|
||||
macro_rules! zip {
|
||||
($x: expr) => ($x);
|
||||
($x: expr, $($y: expr), +) => (
|
||||
$x.iter().zip(
|
||||
zip!($($y), +))
|
||||
)
|
||||
}
|
||||
|
||||
pub struct Transaction {
|
||||
pub calls: Vec<ContractCall>,
|
||||
pub proofs: Vec<Vec<Proof>>,
|
||||
pub signatures: Vec<Vec<Signature>>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
/// Verify ZK contracts for the entire tx
|
||||
/// In real code, we could parallelize this for loop
|
||||
/// TODO: fix use of unwrap with Result type stuff
|
||||
pub fn zk_verify(
|
||||
&self,
|
||||
zk_bins: &ZkContractTable,
|
||||
zkpub_table: &Vec<Vec<(String, Vec<pallas::Base>)>>,
|
||||
) -> Result<()> {
|
||||
assert_eq!(
|
||||
self.calls.len(),
|
||||
self.proofs.len(),
|
||||
"calls.len()={} and proofs.len()={} do not match",
|
||||
self.calls.len(),
|
||||
self.proofs.len()
|
||||
);
|
||||
assert_eq!(
|
||||
self.calls.len(),
|
||||
zkpub_table.len(),
|
||||
"calls.len()={} and zkpub_table.len()={} do not match",
|
||||
self.calls.len(),
|
||||
zkpub_table.len()
|
||||
);
|
||||
for (call, (proofs, pubvals)) in zip!(self.calls, self.proofs, zkpub_table) {
|
||||
assert_eq!(
|
||||
proofs.len(),
|
||||
pubvals.len(),
|
||||
"proofs.len()={} and pubvals.len()={} do not match",
|
||||
proofs.len(),
|
||||
pubvals.len()
|
||||
);
|
||||
|
||||
for (i, (proof, (key, public_vals))) in proofs.iter().zip(pubvals.iter()).enumerate() {
|
||||
match zk_bins.lookup(key).unwrap() {
|
||||
ZkContractInfo::Binary(info) => {
|
||||
let verifying_key = &info.verifying_key;
|
||||
let verify_result = proof.verify(verifying_key, public_vals);
|
||||
if verify_result.is_err() {
|
||||
return Err(ProofVerifyFailed(key.to_string()).into())
|
||||
}
|
||||
//assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
|
||||
}
|
||||
ZkContractInfo::Native(info) => {
|
||||
let verifying_key = &info.verifying_key;
|
||||
let verify_result = proof.verify(verifying_key, public_vals);
|
||||
if verify_result.is_err() {
|
||||
return Err(ProofVerifyFailed(key.to_string()).into())
|
||||
}
|
||||
//assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
|
||||
}
|
||||
};
|
||||
debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_sigs(&self, sigpub_table: &Vec<Vec<pallas::Point>>) -> Result<()> {
|
||||
let mut tx_data = Vec::new();
|
||||
self.calls.encode(&mut tx_data)?;
|
||||
self.proofs.encode(&mut tx_data)?;
|
||||
// TODO: Hash it and use the hash as the signing data
|
||||
// let sighash = ...
|
||||
|
||||
for (i, (signatures, signature_public_keys)) in
|
||||
self.signatures.iter().zip(sigpub_table.iter()).enumerate()
|
||||
{
|
||||
for (signature_pub_key, signature) in signature_public_keys.iter().zip(signatures) {
|
||||
let signature_pub_key = PublicKey::from(*signature_pub_key);
|
||||
let verify_result = signature_pub_key.verify(&tx_data[..], &signature);
|
||||
assert!(verify_result, "verify sigs[{}] failed", i);
|
||||
}
|
||||
debug!(target: "demo", "verify_sigs({}) passed", i);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
/* 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 std::{any::Any, collections::HashMap, hash::Hasher};
|
||||
|
||||
use darkfi_sdk::crypto::{
|
||||
schnorr::{SchnorrPublic, SchnorrSecret, Signature},
|
||||
PublicKey, SecretKey,
|
||||
};
|
||||
use darkfi_serial::Encodable;
|
||||
use lazy_static::lazy_static;
|
||||
use log::debug;
|
||||
use pasta_curves::{
|
||||
group::ff::{Field, PrimeField},
|
||||
pallas,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use darkfi::{
|
||||
crypto::{
|
||||
proof::{ProvingKey, VerifyingKey},
|
||||
types::DrkCircuitField,
|
||||
Proof,
|
||||
},
|
||||
zk::{vm::ZkCircuit, vm_stack::empty_witnesses},
|
||||
zkas::decoder::ZkBinary,
|
||||
};
|
||||
|
||||
use crate::error::{DaoError, DaoResult};
|
||||
|
||||
// /// Parse pallas::Base from a base58-encoded string
|
||||
// pub fn parse_b58(s: &str) -> std::result::Result<pallas::Base, darkfi::Error> {
|
||||
// let bytes = bs58::decode(s).into_vec()?;
|
||||
// if bytes.len() != 32 {
|
||||
// return Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string"))
|
||||
// }
|
||||
|
||||
// let ret = pallas::Base::from_repr(bytes.try_into().unwrap());
|
||||
// if ret.is_some().unwrap_u8() == 1 {
|
||||
// return Ok(ret.unwrap())
|
||||
// }
|
||||
|
||||
// Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string"))
|
||||
// }
|
||||
|
||||
// The token of the DAO treasury.
|
||||
lazy_static! {
|
||||
pub static ref DRK_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
|
||||
// Governance tokens that are airdropped to users to operate the DAO.
|
||||
lazy_static! {
|
||||
pub static ref GOV_ID: pallas::Base = pallas::Base::random(&mut OsRng);
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub struct HashableBase(pub pallas::Base);
|
||||
|
||||
impl std::hash::Hash for HashableBase {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
let bytes = self.0.to_repr();
|
||||
bytes.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ZkBinaryContractInfo {
|
||||
pub k_param: u32,
|
||||
pub bincode: ZkBinary,
|
||||
pub proving_key: ProvingKey,
|
||||
pub verifying_key: VerifyingKey,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ZkNativeContractInfo {
|
||||
pub proving_key: ProvingKey,
|
||||
pub verifying_key: VerifyingKey,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ZkContractInfo {
|
||||
Binary(ZkBinaryContractInfo),
|
||||
Native(ZkNativeContractInfo),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ZkContractTable {
|
||||
// Key will be a hash of zk binary contract on chain
|
||||
table: HashMap<String, ZkContractInfo>,
|
||||
}
|
||||
|
||||
impl ZkContractTable {
|
||||
pub fn new() -> Self {
|
||||
Self { table: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) {
|
||||
let witnesses = empty_witnesses(&bincode);
|
||||
let circuit = ZkCircuit::new(witnesses, bincode.clone());
|
||||
let proving_key = ProvingKey::build(k_param, &circuit);
|
||||
let verifying_key = VerifyingKey::build(k_param, &circuit);
|
||||
let info = ZkContractInfo::Binary(ZkBinaryContractInfo {
|
||||
k_param,
|
||||
bincode,
|
||||
proving_key,
|
||||
verifying_key,
|
||||
});
|
||||
self.table.insert(key, info);
|
||||
}
|
||||
|
||||
pub fn add_native(
|
||||
&mut self,
|
||||
key: String,
|
||||
proving_key: ProvingKey,
|
||||
verifying_key: VerifyingKey,
|
||||
) {
|
||||
self.table.insert(
|
||||
key,
|
||||
ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> {
|
||||
self.table.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Transaction {
|
||||
pub func_calls: Vec<FuncCall>,
|
||||
pub signatures: Vec<Vec<Signature>>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
/// Verify ZK contracts for the entire tx
|
||||
/// In real code, we could parallelize this for loop
|
||||
/// TODO: fix use of unwrap with Result type stuff
|
||||
pub fn zk_verify(&self, zk_bins: &ZkContractTable) -> DaoResult<()> {
|
||||
for func_call in &self.func_calls {
|
||||
let proofs_public_vals = &func_call.call_data.zk_public_values();
|
||||
|
||||
assert_eq!(
|
||||
proofs_public_vals.len(),
|
||||
func_call.proofs.len(),
|
||||
"proof_public_vals.len()={} and func_call.proofs.len()={} do not match",
|
||||
proofs_public_vals.len(),
|
||||
func_call.proofs.len()
|
||||
);
|
||||
for (i, (proof, (key, public_vals))) in
|
||||
func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate()
|
||||
{
|
||||
match zk_bins.lookup(key).unwrap() {
|
||||
ZkContractInfo::Binary(info) => {
|
||||
let verifying_key = &info.verifying_key;
|
||||
let verify_result = proof.verify(&verifying_key, public_vals);
|
||||
if verify_result.is_err() {
|
||||
return Err(DaoError::VerifyProofFailed(i, key.to_string()))
|
||||
}
|
||||
//assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
|
||||
}
|
||||
ZkContractInfo::Native(info) => {
|
||||
let verifying_key = &info.verifying_key;
|
||||
let verify_result = proof.verify(&verifying_key, public_vals);
|
||||
if verify_result.is_err() {
|
||||
return Err(DaoError::VerifyProofFailed(i, key.to_string()))
|
||||
}
|
||||
//assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key);
|
||||
}
|
||||
};
|
||||
debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_sigs(&self) {
|
||||
let mut unsigned_tx_data = vec![];
|
||||
for (i, (func_call, signatures)) in
|
||||
self.func_calls.iter().zip(self.signatures.clone()).enumerate()
|
||||
{
|
||||
func_call.encode(&mut unsigned_tx_data).expect("failed to encode data");
|
||||
let signature_pub_keys = func_call.call_data.signature_public_keys();
|
||||
for (signature_pub_key, signature) in signature_pub_keys.iter().zip(signatures) {
|
||||
let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature);
|
||||
assert!(verify_result, "verify sigs[{}] failed", i);
|
||||
}
|
||||
debug!(target: "demo", "verify_sigs({}) passed", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sign(signature_secrets: Vec<SecretKey>, func_call: &FuncCall) -> Vec<Signature> {
|
||||
let mut signatures = vec![];
|
||||
let mut unsigned_tx_data = vec![];
|
||||
for signature_secret in signature_secrets {
|
||||
func_call.encode(&mut unsigned_tx_data).expect("failed to encode data");
|
||||
let signature = signature_secret.sign(&mut OsRng, &unsigned_tx_data[..]);
|
||||
signatures.push(signature);
|
||||
}
|
||||
signatures
|
||||
}
|
||||
|
||||
type ContractId = pallas::Base;
|
||||
type FuncId = pallas::Base;
|
||||
|
||||
pub struct FuncCall {
|
||||
pub contract_id: ContractId,
|
||||
pub func_id: FuncId,
|
||||
pub call_data: Box<dyn CallDataBase + Send + Sync>,
|
||||
pub proofs: Vec<Proof>,
|
||||
}
|
||||
|
||||
impl Encodable for FuncCall {
|
||||
fn encode<W: std::io::Write>(&self, mut w: W) -> std::result::Result<usize, std::io::Error> {
|
||||
let mut len = 0;
|
||||
len += self.contract_id.encode(&mut w)?;
|
||||
len += self.func_id.encode(&mut w)?;
|
||||
len += self.proofs.encode(&mut w)?;
|
||||
len += self.call_data.encode_bytes(&mut w)?;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CallDataBase {
|
||||
// Public values for verifying the proofs
|
||||
// Needed so we can convert internal types so they can be used in Proof::verify()
|
||||
fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)>;
|
||||
|
||||
// For upcasting to CallData itself so it can be read in state_transition()
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
// Public keys we will use to verify transaction signatures.
|
||||
fn signature_public_keys(&self) -> Vec<PublicKey>;
|
||||
|
||||
fn encode_bytes(
|
||||
&self,
|
||||
writer: &mut dyn std::io::Write,
|
||||
) -> std::result::Result<usize, std::io::Error>;
|
||||
}
|
||||
|
||||
type GenericContractState = Box<dyn Any + Send>;
|
||||
|
||||
pub struct StateRegistry {
|
||||
pub states: HashMap<HashableBase, GenericContractState>,
|
||||
}
|
||||
|
||||
impl StateRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self { states: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn register(&mut self, contract_id: ContractId, state: GenericContractState) {
|
||||
debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id);
|
||||
self.states.insert(HashableBase(contract_id), state);
|
||||
}
|
||||
|
||||
pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> {
|
||||
self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut())
|
||||
}
|
||||
|
||||
pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> {
|
||||
self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UpdateBase {
|
||||
fn apply(self: Box<Self>, states: &mut StateRegistry);
|
||||
}
|
||||
Reference in New Issue
Block a user