mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
contract/money: Initial outline in src/contract/money.
This commit is contained in:
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -1297,6 +1297,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkfi-money-contract"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"darkfi-sdk",
|
||||
"darkfi-serial",
|
||||
"getrandom 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkfi-sdk"
|
||||
version = "0.3.0"
|
||||
|
||||
@@ -44,6 +44,8 @@ members = [
|
||||
"src/serial/derive",
|
||||
"src/serial/derive-internal",
|
||||
|
||||
"src/contract/money",
|
||||
|
||||
"example/dchat",
|
||||
"example/dao",
|
||||
]
|
||||
|
||||
1
src/contract/README.md
Normal file
1
src/contract/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This directory contains native WASM contracts on DarkFi.
|
||||
2
src/contract/money/.gitignore
vendored
Normal file
2
src/contract/money/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
money_contract.wasm
|
||||
proof/*.zk.bin
|
||||
18
src/contract/money/Cargo.toml
Normal file
18
src/contract/money/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "darkfi-money-contract"
|
||||
version = "0.3.0"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
darkfi-sdk = { path = "../../sdk" }
|
||||
darkfi-serial = { path = "../../serial", features = ["derive", "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"] }
|
||||
37
src/contract/money/Makefile
Normal file
37
src/contract/money/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
# Cargo binary
|
||||
CARGO ?= cargo
|
||||
|
||||
# Zkas binary
|
||||
ZKAS ?= ../../../zkas
|
||||
|
||||
# zkas circuit source files
|
||||
ZKAS_SRC = $(shell find proof -type f -name '*.zk')
|
||||
|
||||
# wasm source files
|
||||
WASM_SRC = \
|
||||
$(shell find src -type f) \
|
||||
$(shell find ../../sdk/src -type f) \
|
||||
$(shell find ../../serial/src -type f)
|
||||
|
||||
# zkas circuit bin files
|
||||
ZKAS_BIN = $(ZKAS_SRC:=.bin)
|
||||
|
||||
# Contract WASM binaries
|
||||
WASM_BIN = money_contract.wasm
|
||||
|
||||
all: $(ZKAS_BIN) $(WASM_BIN)
|
||||
|
||||
$(ZKAS_BIN): $(ZKAS_SRC)
|
||||
$(ZKAS) $(basename $@) -o $@
|
||||
|
||||
money_contract.wasm: $(ZKAS_BIN) $(WASM_SRC)
|
||||
$(CARGO) build --release --package darkfi-money-contract --target wasm32-unknown-unknown
|
||||
cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_money_contract.wasm $@
|
||||
|
||||
test:
|
||||
|
||||
clean:
|
||||
rm -f $(ZKAS_BIN) $(WASM_BIN)
|
||||
|
||||
# We always rebuild the wasm no matter what
|
||||
.PHONY: $(WASM_BIN) all test clean
|
||||
66
src/contract/money/proof/burn.zk
Normal file
66
src/contract/money/proof/burn.zk
Normal file
@@ -0,0 +1,66 @@
|
||||
constant "Burn" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
EcFixedPointBase NULLIFIER_K,
|
||||
}
|
||||
|
||||
contract "Burn" {
|
||||
Base secret,
|
||||
Base serial,
|
||||
Base value,
|
||||
Base token,
|
||||
Base coin_blind,
|
||||
Scalar value_blind,
|
||||
Scalar token_blind,
|
||||
Uint32 leaf_pos,
|
||||
MerklePath path,
|
||||
Base signature_secret,
|
||||
}
|
||||
|
||||
circuit "Burn" {
|
||||
# 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);
|
||||
|
||||
# Pedersen commitment for coin's token ID
|
||||
tcv = ec_mul_base(token, NULLIFIER_K);
|
||||
tcr = ec_mul(token_blind, VALUE_COMMIT_RANDOM);
|
||||
token_commit = ec_add(tcv, tcr);
|
||||
# Since token_commit is also a curve point, we'll do the same
|
||||
# coordinate dance:
|
||||
token_commit_x = ec_get_x(token_commit);
|
||||
token_commit_y = ec_get_y(token_commit);
|
||||
constrain_instance(token_commit_x);
|
||||
constrain_instance(token_commit_y);
|
||||
|
||||
# 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, 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.
|
||||
}
|
||||
46
src/contract/money/proof/mint.zk
Normal file
46
src/contract/money/proof/mint.zk
Normal file
@@ -0,0 +1,46 @@
|
||||
constant "Mint" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
EcFixedPointBase NULLIFIER_K,
|
||||
}
|
||||
|
||||
contract "Mint" {
|
||||
Base pub_x,
|
||||
Base pub_y,
|
||||
Base value,
|
||||
Base token,
|
||||
Base serial,
|
||||
Base coin_blind,
|
||||
Scalar value_blind,
|
||||
Scalar token_blind,
|
||||
}
|
||||
|
||||
circuit "Mint" {
|
||||
# Poseidon hash of the coin
|
||||
C = poseidon_hash(pub_x, pub_y, value, token, serial, coin_blind);
|
||||
constrain_instance(C);
|
||||
|
||||
# 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 the 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);
|
||||
|
||||
# Pedersen commitment for coin's token ID
|
||||
tcv = ec_mul_base(token, NULLIFIER_K);
|
||||
tcr = ec_mul(token_blind, VALUE_COMMIT_RANDOM);
|
||||
token_commit = ec_add(tcv, tcr);
|
||||
# Since token_commit is also a curve point, we'll do the same
|
||||
# coordinate dance:
|
||||
token_commit_x = ec_get_x(token_commit);
|
||||
token_commit_y = ec_get_y(token_commit);
|
||||
constrain_instance(token_commit_x);
|
||||
constrain_instance(token_commit_y);
|
||||
|
||||
# At this point we've enforced all of our public inputs.
|
||||
}
|
||||
299
src/contract/money/src/lib.rs
Normal file
299
src/contract/money/src/lib.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 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_contains_key, db_get, db_init, db_lookup, db_set},
|
||||
define_contract,
|
||||
error::{ContractError, ContractResult},
|
||||
merkle::merkle_add,
|
||||
msg,
|
||||
pasta::{arithmetic::CurveAffine, group::Curve, pallas},
|
||||
tx::ContractCall,
|
||||
util::set_return_data,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
/// Functions we allow in this contract
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structures and object definitions
|
||||
pub mod state;
|
||||
use state::{MoneyTransferParams, MoneyTransferUpdate};
|
||||
|
||||
define_contract!(
|
||||
init: init_contract,
|
||||
exec: process_instruction,
|
||||
apply: process_update,
|
||||
metadata: get_metadata
|
||||
);
|
||||
|
||||
// These are the different sled trees that will be created
|
||||
pub const ZKAS_TREE: &str = "zkas";
|
||||
pub const COIN_ROOTS_TREE: &str = "coin_roots";
|
||||
pub const NULLIFIERS_TREE: &str = "nullifiers";
|
||||
pub const INFO_TREE: &str = "info";
|
||||
|
||||
// This is a key inside the info tree
|
||||
pub const COIN_MERKLE_TREE: &str = "coin_tree";
|
||||
pub const FAUCET_PUBKEYS: &str = "faucet_pubkeys";
|
||||
|
||||
/// zkas mint contract namespace
|
||||
pub const ZKAS_MINT_NS: &str = "Mint";
|
||||
/// zkas burn contract namespace
|
||||
pub const ZKAS_BURN_NS: &str = "Burn";
|
||||
|
||||
/// This function runs when the contract is (re)deployed and initialized.
|
||||
fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
|
||||
// The zkas circuits can simply be embedded in the wasm and set up by
|
||||
// the initialization. Note that the tree should then be called "zkas".
|
||||
// The lookups can then be done by `contract_id+zkas+namespace`.
|
||||
let zkas_db = db_init(cid, ZKAS_TREE)?;
|
||||
let mint_bincode = include_bytes!("../proof/mint.zk.bin");
|
||||
let burn_bincode = include_bytes!("../proof/burn.zk.bin");
|
||||
|
||||
/* TODO: Do I really want to make zkas a dependency? Yeah, in the future.
|
||||
For now we take anything.
|
||||
let zkbin = ZkBinary::decode(mint_bincode)?;
|
||||
let mint_namespace = zkbin.namespace.clone();
|
||||
assert_eq!(&mint_namespace, ZKAS_MINT_NS);
|
||||
let zkbin = ZkBinary::decode(burn_bincode)?;
|
||||
let burn_namespace = zkbin.namespace.clone();
|
||||
assert_eq!(&burn_namespace, ZKAS_BURN_NS);
|
||||
db_set(zkas_db, &serialize(&mint_namespace), &mint_bincode[..])?;
|
||||
db_set(zkas_db, &serialize(&burn_namespace), &burn_bincode[..])?;
|
||||
*/
|
||||
|
||||
db_set(zkas_db, &serialize(&ZKAS_MINT_NS.to_string()), &mint_bincode[..])?;
|
||||
db_set(zkas_db, &serialize(&ZKAS_BURN_NS.to_string()), &burn_bincode[..])?;
|
||||
|
||||
// Set up a database tree to hold Merkle roots
|
||||
let _ = db_init(cid, COIN_ROOTS_TREE)?;
|
||||
|
||||
// Set up a database tree to hold nullifiers
|
||||
let _ = db_init(cid, NULLIFIERS_TREE)?;
|
||||
|
||||
// Set up a database tree for arbitrary data
|
||||
let info_db = db_init(cid, INFO_TREE)?;
|
||||
|
||||
// Add a Merkle tree to the info db:
|
||||
let coin_tree = MerkleTree::new(100);
|
||||
let mut coin_tree_data = vec![];
|
||||
// TODO: FIXME: What is this write_u32 doing here?
|
||||
coin_tree_data.write_u32(0)?;
|
||||
coin_tree.encode(&mut coin_tree_data)?;
|
||||
db_set(info_db, &serialize(&COIN_MERKLE_TREE.to_string()), &coin_tree_data)?;
|
||||
|
||||
// Whitelisted faucets
|
||||
let faucet_pubkeys: Vec<PublicKey> = vec![];
|
||||
db_set(info_db, &serialize(&FAUCET_PUBKEYS.to_string()), &serialize(&faucet_pubkeys))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function is used by the VM's host to fetch the necessary metadata for
|
||||
/// verifying signatures and zk proofs.
|
||||
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 params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let mut signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
for input in ¶ms.clear_inputs {
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
}
|
||||
|
||||
for input in ¶ms.inputs {
|
||||
let value_coords = input.value_commit.to_affine().coordinates().unwrap();
|
||||
let token_coords = input.token_commit.to_affine().coordinates().unwrap();
|
||||
let (sig_x, sig_y) = input.signature_public.xy();
|
||||
|
||||
zk_public_values.push((
|
||||
ZKAS_BURN_NS.to_string(),
|
||||
vec![
|
||||
input.nullifier.inner(),
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
input.merkle_root.inner(),
|
||||
input.user_data_enc,
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
}
|
||||
|
||||
for output in ¶ms.outputs {
|
||||
let value_coords = output.value_commit.to_affine().coordinates().unwrap();
|
||||
let token_coords = output.token_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_public_values.push((
|
||||
ZKAS_MINT_NS.to_string(),
|
||||
vec![
|
||||
output.coin.inner(),
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
*token_coords.x(),
|
||||
*token_coords.y(),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
// Using this, we pass the above data to the host.
|
||||
set_return_data(&metadata)?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function verifies a state transition and produces an
|
||||
/// update if everything is successful.
|
||||
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 params: MoneyTransferParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let info_db = db_lookup(cid, INFO_TREE)?;
|
||||
let nullifier_db = db_lookup(cid, NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, COIN_ROOTS_TREE)?;
|
||||
|
||||
let Some(faucet_pubkeys) = db_get(info_db, &serialize(&FAUCET_PUBKEYS.to_string()))? else {
|
||||
msg!("[Transfer] Error: Missing faucet pubkeys from info db");
|
||||
return Err(ContractError::Internal);
|
||||
};
|
||||
let faucet_pubkeys: Vec<PublicKey> = deserialize(&faucet_pubkeys)?;
|
||||
|
||||
// State transition for payments
|
||||
msg!("[Transfer] Iterating over clear inputs");
|
||||
for (i, input) in params.clear_inputs.iter().enumerate() {
|
||||
let pk = input.signature_public;
|
||||
|
||||
if !faucet_pubkeys.contains(&pk) {
|
||||
msg!("[Transfer] Error: Clear input {} has invalid faucet pubkey", i);
|
||||
return Err(ContractError::Custom(20))
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_coin_roots = vec![];
|
||||
let mut new_nullifiers = vec![];
|
||||
|
||||
msg!("[Transfer] Iterating over anonymous inputs");
|
||||
for (i, input) in params.inputs.iter().enumerate() {
|
||||
// The Merkle root is used to know whether this is a coin that existed
|
||||
// in a previous state.
|
||||
if new_coin_roots.contains(&input.merkle_root) ||
|
||||
db_contains_key(coin_roots_db, &serialize(&input.merkle_root))?
|
||||
{
|
||||
msg!("[Transfer] Error: Duplicate Merkle root found in input {}", i);
|
||||
return Err(ContractError::Custom(21))
|
||||
}
|
||||
|
||||
// The nullifiers should not already exist. It is the double-spend protection.
|
||||
if new_nullifiers.contains(&input.nullifier) ||
|
||||
db_contains_key(nullifier_db, &serialize(&input.nullifier))?
|
||||
{
|
||||
msg!("[Transfer] Error: Duplicate nullifier found in input {}", i);
|
||||
return Err(ContractError::Custom(22))
|
||||
}
|
||||
|
||||
new_coin_roots.push(input.merkle_root);
|
||||
new_nullifiers.push(input.nullifier);
|
||||
}
|
||||
|
||||
// Newly created coins for this transaction are in the outputs.
|
||||
let new_coins = Vec::with_capacity(params.outputs.len());
|
||||
for (i, output) in params.outputs.iter().enumerate() {
|
||||
// TODO: Should we have coins in a sled tree too to check dupes?
|
||||
if new_coins.contains(&output.coin) {
|
||||
msg!("[Transfer] Error: Duplicate coin found in output {}", i);
|
||||
return Err(ContractError::Custom(23))
|
||||
}
|
||||
}
|
||||
|
||||
// Create a state update
|
||||
let update = MoneyTransferUpdate { nullifiers: new_nullifiers, coins: new_coins };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(MoneyFunction::Transfer as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[Transfer] State update set!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
|
||||
match MoneyFunction::from(update_data[0]) {
|
||||
MoneyFunction::Transfer => {
|
||||
let update: MoneyTransferUpdate = deserialize(&update_data[1..])?;
|
||||
|
||||
let info_db = db_lookup(cid, INFO_TREE)?;
|
||||
let nullifiers_db = db_lookup(cid, NULLIFIERS_TREE)?;
|
||||
let coin_roots_db = db_lookup(cid, COIN_ROOTS_TREE)?;
|
||||
|
||||
for nullifier in update.nullifiers {
|
||||
db_set(nullifiers_db, &serialize(&nullifier), &[])?;
|
||||
}
|
||||
|
||||
for coin in update.coins {
|
||||
// TODO: merkle_add() should take a list of coins and batch add them for efficiency
|
||||
merkle_add(
|
||||
info_db,
|
||||
coin_roots_db,
|
||||
&serialize(&COIN_MERKLE_TREE.to_string()),
|
||||
&MerkleNode::from(coin.inner()),
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/contract/money/src/state.rs
Normal file
95
src/contract/money/src/state.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 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::{ValueBlind, ValueCommit},
|
||||
Coin, MerkleNode, Nullifier, PublicKey, TokenId,
|
||||
},
|
||||
pasta::pallas,
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
/// Inputs and outputs for a payment
|
||||
#[derive(Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct MoneyTransferParams {
|
||||
/// Clear inputs
|
||||
pub clear_inputs: Vec<ClearInput>,
|
||||
/// Anonymous inputs
|
||||
pub inputs: Vec<Input>,
|
||||
/// Anonymous outputs
|
||||
pub outputs: Vec<Output>,
|
||||
}
|
||||
|
||||
/// State update produced by a payment
|
||||
#[derive(Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct MoneyTransferUpdate {
|
||||
/// Revealed nullifiers
|
||||
pub nullifiers: Vec<Nullifier>,
|
||||
/// Minted coins
|
||||
pub coins: Vec<Coin>,
|
||||
}
|
||||
|
||||
/// A transaction's clear input
|
||||
#[derive(Debug, 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: ValueBlind,
|
||||
/// Blinding factor for `token_id`
|
||||
pub token_blind: ValueBlind,
|
||||
/// Public key for the signature
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous input
|
||||
#[derive(Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct Input {
|
||||
/// Pedersen commitment for the input's value
|
||||
pub value_commit: ValueCommit,
|
||||
/// Pedersen commitment for the input's token ID
|
||||
pub token_commit: ValueCommit,
|
||||
/// Revealed nullifier
|
||||
pub nullifier: Nullifier,
|
||||
/// Revealed Merkle root
|
||||
pub merkle_root: MerkleNode,
|
||||
/// spend hook (TODO: document)
|
||||
pub spend_hook: pallas::Base,
|
||||
/// user data enc (TODO: document)
|
||||
pub user_data_enc: pallas::Base,
|
||||
/// Public key for the signature
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
/// A transaction's anonymous output
|
||||
#[derive(Debug, SerialEncodable, SerialDecodable)]
|
||||
pub struct Output {
|
||||
/// Pedersen commitment for the output's value
|
||||
pub value_commit: ValueCommit,
|
||||
/// Pedersen commitment for the output's token ID
|
||||
pub token_commit: ValueCommit,
|
||||
/// Minted coin
|
||||
pub coin: Coin,
|
||||
/// The encrypted note ciphertext
|
||||
pub ciphertext: Vec<u8>,
|
||||
/// The ephemeral public key
|
||||
pub ephem_public: PublicKey,
|
||||
}
|
||||
85
src/sdk/src/crypto/coin.rs
Normal file
85
src/sdk/src/crypto/coin.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 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 core::{fmt, str::FromStr};
|
||||
use std::io;
|
||||
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
use pasta_curves::{group::ff::PrimeField, pallas};
|
||||
|
||||
/// The `Coin` is represented as a base field element.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, SerialEncodable, SerialDecodable)]
|
||||
pub struct Coin(pallas::Base);
|
||||
|
||||
impl Coin {
|
||||
/// Reference the raw inner base field element
|
||||
pub fn inner(&self) -> pallas::Base {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Try to create a `Coin` type from the given 32 bytes.
|
||||
/// Returns `Some` if the bytes fit in the base field, and `None` if not.
|
||||
pub fn from_bytes(bytes: [u8; 32]) -> Option<Self> {
|
||||
let n = pallas::Base::from_repr(bytes);
|
||||
match bool::from(n.is_some()) {
|
||||
true => Some(Self(n.unwrap())),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the `Coin` type into 32 raw bytes
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0.to_repr()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pallas::Base> for Coin {
|
||||
fn from(x: pallas::Base) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Coin {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", bs58::encode(self.to_bytes()).into_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Coin {
|
||||
type Err = io::Error;
|
||||
|
||||
/// Tries to decode a base58 string into a `Coin` type.
|
||||
/// This string is the same string received by calling `Coin::to_string()`.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let bytes = match bs58::decode(s).into_vec() {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
};
|
||||
|
||||
if bytes.len() != 32 {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Length of decoded bytes is not 32"))
|
||||
}
|
||||
|
||||
if let Some(coin) = Self::from_bytes(bytes.try_into().unwrap()) {
|
||||
return Ok(coin)
|
||||
}
|
||||
|
||||
Err(io::Error::new(io::ErrorKind::Other, "Invalid bytes for Coin"))
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,10 @@ pub use keypair::{Keypair, PublicKey, SecretKey};
|
||||
pub mod address;
|
||||
pub use address::Address;
|
||||
|
||||
/// Coin definitions and methods
|
||||
pub mod coin;
|
||||
pub use coin::Coin;
|
||||
|
||||
/// Contract ID definitions and methods
|
||||
pub mod contract_id;
|
||||
pub use contract_id::ContractId;
|
||||
|
||||
Reference in New Issue
Block a user