use darkfi::{
zk::{Proof, ProvingKey},
zkas::ZkBinary,
ClientFailed, Result,
};
use darkfi_sdk::{
crypto::{
note::AeadEncryptedNote, pasta_prelude::*, BaseBlind, Blind, FuncId, MerkleTree, PublicKey,
ScalarBlind, SecretKey,
},
pasta::pallas,
};
use darkfi_serial::serialize;
use log::{debug, error};
use rand::rngs::OsRng;
use crate::{
client::{
transfer_v1::{
proof::{create_transfer_burn_proof, create_transfer_mint_proof},
TransferCallInput, TransferCallOutput,
},
MoneyNote, OwnCoin,
},
model::{Input, MoneyTransferParamsV1, Output, TokenId},
};
pub struct SwapCallDebris {
pub params: MoneyTransferParamsV1,
pub proofs: Vec<Proof>,
pub signature_secret: SecretKey,
}
pub struct SwapCallBuilder {
pub pubkey: PublicKey,
pub value_send: u64,
pub token_id_send: TokenId,
pub value_recv: u64,
pub token_id_recv: TokenId,
pub user_data_blind_send: BaseBlind,
pub spend_hook_recv: FuncId,
pub user_data_recv: pallas::Base,
pub value_blinds: [ScalarBlind; 2],
pub token_blinds: [BaseBlind; 2],
pub coin: OwnCoin,
pub tree: MerkleTree,
pub mint_zkbin: ZkBinary,
pub mint_pk: ProvingKey,
pub burn_zkbin: ZkBinary,
pub burn_pk: ProvingKey,
}
impl SwapCallBuilder {
pub fn build(&self) -> Result<SwapCallDebris> {
debug!(target: "contract::money::client::swap", "Building half of Money::OtcSwapV1 contract call");
if self.value_send == 0 {
error!(target: "contract::money::client::swap", "Error: Value send is 0");
return Err(ClientFailed::InvalidAmount(self.value_send).into())
}
if self.value_recv == 0 {
error!(target: "contract::money::client::swap", "Error: Value receive is 0");
return Err(ClientFailed::InvalidAmount(self.value_recv).into())
}
if self.token_id_send.inner() == pallas::Base::ZERO {
error!(target: "contract::money::client::swap", "Error: Token send is ZERO");
return Err(ClientFailed::InvalidTokenId(self.token_id_send.to_string()).into())
}
if self.token_id_recv.inner() == pallas::Base::ZERO {
error!(target: "contract::money::client::swap", "Error: Token receive is ZERO");
return Err(ClientFailed::InvalidTokenId(self.token_id_recv.to_string()).into())
}
if self.coin.note.value != self.value_send {
return Err(ClientFailed::InvalidAmount(self.coin.note.value).into())
}
if self.coin.note.token_id != self.token_id_send {
return Err(ClientFailed::InvalidTokenId(self.coin.note.token_id.to_string()).into())
}
let input = TransferCallInput {
coin: self.coin.clone(),
merkle_path: self.tree.witness(self.coin.leaf_position, 0).unwrap(),
user_data_blind: self.user_data_blind_send,
};
let output = TransferCallOutput {
public_key: self.pubkey,
value: self.value_recv,
token_id: self.token_id_recv,
spend_hook: FuncId::none(),
user_data: pallas::Base::ZERO,
blind: Blind::random(&mut OsRng),
};
let mut params = MoneyTransferParamsV1 { inputs: vec![], outputs: vec![] };
let signature_secret = SecretKey::random(&mut OsRng);
let mut proofs = vec![];
debug!(target: "contract::money::client::swap", "Creating burn proof for input");
let (proof, public_inputs) = create_transfer_burn_proof(
&self.burn_zkbin,
&self.burn_pk,
&input,
self.value_blinds[0],
self.token_blinds[0],
signature_secret,
)?;
params.inputs.push(Input {
value_commit: public_inputs.value_commit,
token_commit: public_inputs.token_commit,
nullifier: public_inputs.nullifier,
merkle_root: public_inputs.merkle_root,
user_data_enc: public_inputs.user_data_enc,
signature_public: public_inputs.signature_public,
});
proofs.push(proof);
let coin_blind = Blind::random(&mut OsRng);
debug!(target: "contract::money::client::swap", "Creating mint proof for output");
let (proof, public_inputs) = create_transfer_mint_proof(
&self.mint_zkbin,
&self.mint_pk,
&output,
self.value_blinds[1],
self.token_blinds[1],
self.spend_hook_recv,
self.user_data_recv,
coin_blind,
)?;
proofs.push(proof);
let note = MoneyNote {
value: output.value,
token_id: output.token_id,
spend_hook: self.spend_hook_recv,
user_data: self.user_data_recv,
coin_blind,
value_blind: self.value_blinds[1],
token_blind: self.token_blinds[1],
memo: serialize(&signature_secret),
};
let encrypted_note = AeadEncryptedNote::encrypt(¬e, &self.pubkey, &mut OsRng)?;
params.outputs.push(Output {
value_commit: public_inputs.value_commit,
token_commit: public_inputs.token_commit,
coin: public_inputs.coin,
note: encrypted_note,
});
let debris = SwapCallDebris { params, proofs, signature_secret };
Ok(debris)
}
}