/* 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 .
*/
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 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,
/// Anonymous inputs
pub inputs: Vec ,
/// Anonymous outputs
pub outputs: Vec,
}
#[derive(SerialEncodable, SerialDecodable)]
pub struct MoneyTransferUpdate {
/// Nullifiers
pub nullifiers: Vec,
/// Coins
pub coins: Vec,
}
/// 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,
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) = 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)> = Vec::new();
let mut signature_public_keys: Vec = 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) = 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(())
}