1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
1819//! This API is crufty. Please rework it into something nice to read and nice to use.
2021use darkfi::{
22 zk::{Proof, ProvingKey},
23 zkas::ZkBinary,
24 ClientFailed, Result,
25};
26use darkfi_sdk::{
27 crypto::{
28 note::AeadEncryptedNote, pasta_prelude::*, BaseBlind, Blind, FuncId, MerkleTree, PublicKey,
29 ScalarBlind, SecretKey,
30 },
31 pasta::pallas,
32};
33use darkfi_serial::serialize;
34use log::{debug, error};
35use rand::rngs::OsRng;
3637use crate::{
38 client::{
39 transfer_v1::{
40 proof::{create_transfer_burn_proof, create_transfer_mint_proof},
41 TransferCallInput, TransferCallOutput,
42 },
43 MoneyNote, OwnCoin,
44 },
45 model::{Input, MoneyTransferParamsV1, Output, TokenId},
46};
4748pub struct SwapCallDebris {
49pub params: MoneyTransferParamsV1,
50pub proofs: Vec<Proof>,
51pub signature_secret: SecretKey,
52}
5354/// Struct holding necessary information to build a `Money::OtcSwapV1` contract call.
55/// This is used to build half of the swap transaction, so both parties have to build
56/// their halves and combine them.
57pub struct SwapCallBuilder {
58/// Party's public key for receiving the output
59pub pubkey: PublicKey,
60/// The value of the party's input to swap (send)
61pub value_send: u64,
62/// The token ID of the party's input to swap (send)
63pub token_id_send: TokenId,
64/// The value of the party's output to receive
65pub value_recv: u64,
66/// The token ID of the party's output to receive
67pub token_id_recv: TokenId,
68/// User data blind for the party's input
69pub user_data_blind_send: BaseBlind,
70/// Spend hook for the party's output
71pub spend_hook_recv: FuncId,
72/// User data for the party's output
73pub user_data_recv: pallas::Base,
74/// The blinds to be used for value pedersen commitments
75 /// `[0]` is used for input 0 and output 1, and `[1]` is
76 /// used for input 1 and output 0. The same applies to
77 /// `token_blinds`.
78pub value_blinds: [ScalarBlind; 2],
79/// The blinds to be used for token ID pedersen commitments
80pub token_blinds: [BaseBlind; 2],
81/// The coin to be used as the input to the swap
82pub coin: OwnCoin,
83/// Merkle tree of coins used to create inclusion proofs
84pub tree: MerkleTree,
85/// `Mint_V1` zkas circuit ZkBinary
86pub mint_zkbin: ZkBinary,
87/// Proving key for the `Mint_V1` zk circuit
88pub mint_pk: ProvingKey,
89/// `Burn_V1` zkas circuit ZkBinary
90pub burn_zkbin: ZkBinary,
91/// Proving key for the `Burn_V1` zk circuit
92pub burn_pk: ProvingKey,
93}
9495impl SwapCallBuilder {
96pub fn build(&self) -> Result<SwapCallDebris> {
97debug!(target: "contract::money::client::swap", "Building half of Money::OtcSwapV1 contract call");
98if self.value_send == 0 {
99error!(target: "contract::money::client::swap", "Error: Value send is 0");
100return Err(ClientFailed::InvalidAmount(self.value_send).into())
101 }
102103if self.value_recv == 0 {
104error!(target: "contract::money::client::swap", "Error: Value receive is 0");
105return Err(ClientFailed::InvalidAmount(self.value_recv).into())
106 }
107108if self.token_id_send.inner() == pallas::Base::ZERO {
109error!(target: "contract::money::client::swap", "Error: Token send is ZERO");
110return Err(ClientFailed::InvalidTokenId(self.token_id_send.to_string()).into())
111 }
112113if self.token_id_recv.inner() == pallas::Base::ZERO {
114error!(target: "contract::money::client::swap", "Error: Token receive is ZERO");
115return Err(ClientFailed::InvalidTokenId(self.token_id_recv.to_string()).into())
116 }
117118if self.coin.note.value != self.value_send {
119return Err(ClientFailed::InvalidAmount(self.coin.note.value).into())
120 }
121122if self.coin.note.token_id != self.token_id_send {
123return Err(ClientFailed::InvalidTokenId(self.coin.note.token_id.to_string()).into())
124 }
125126let input = TransferCallInput {
127 coin: self.coin.clone(),
128 merkle_path: self.tree.witness(self.coin.leaf_position, 0).unwrap(),
129 user_data_blind: self.user_data_blind_send,
130 };
131132let output = TransferCallOutput {
133 public_key: self.pubkey,
134 value: self.value_recv,
135 token_id: self.token_id_recv,
136 spend_hook: FuncId::none(),
137 user_data: pallas::Base::ZERO,
138 blind: Blind::random(&mut OsRng),
139 };
140141// Now we fill this with necessary stuff
142let mut params = MoneyTransferParamsV1 { inputs: vec![], outputs: vec![] };
143144// Create a new ephemeral secret key
145let signature_secret = SecretKey::random(&mut OsRng);
146147let mut proofs = vec![];
148debug!(target: "contract::money::client::swap", "Creating burn proof for input");
149let (proof, public_inputs) = create_transfer_burn_proof(
150&self.burn_zkbin,
151&self.burn_pk,
152&input,
153self.value_blinds[0],
154self.token_blinds[0],
155 signature_secret,
156 )?;
157158 params.inputs.push(Input {
159 value_commit: public_inputs.value_commit,
160 token_commit: public_inputs.token_commit,
161 nullifier: public_inputs.nullifier,
162 merkle_root: public_inputs.merkle_root,
163 user_data_enc: public_inputs.user_data_enc,
164 signature_public: public_inputs.signature_public,
165 });
166167 proofs.push(proof);
168169// For the output, we create a new coin blind
170let coin_blind = Blind::random(&mut OsRng);
171172debug!(target: "contract::money::client::swap", "Creating mint proof for output");
173let (proof, public_inputs) = create_transfer_mint_proof(
174&self.mint_zkbin,
175&self.mint_pk,
176&output,
177self.value_blinds[1],
178self.token_blinds[1],
179self.spend_hook_recv,
180self.user_data_recv,
181 coin_blind,
182 )?;
183184 proofs.push(proof);
185186// Encrypted note
187let note = MoneyNote {
188 value: output.value,
189 token_id: output.token_id,
190 spend_hook: self.spend_hook_recv,
191 user_data: self.user_data_recv,
192 coin_blind,
193 value_blind: self.value_blinds[1],
194 token_blind: self.token_blinds[1],
195// Here we store our secret key we use for signing
196memo: serialize(&signature_secret),
197 };
198199let encrypted_note = AeadEncryptedNote::encrypt(¬e, &self.pubkey, &mut OsRng)?;
200201 params.outputs.push(Output {
202 value_commit: public_inputs.value_commit,
203 token_commit: public_inputs.token_commit,
204 coin: public_inputs.coin,
205 note: encrypted_note,
206 });
207208// Now we should have all the params, zk proofs, and signature secrets.
209 // We return it all and let the caller deal with it.
210let debris = SwapCallDebris { params, proofs, signature_secret };
211Ok(debris)
212 }
213}