darkfi_money_contract/client/
swap_v1.rs

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 */
18
19//! This API is crufty. Please rework it into something nice to read and nice to use.
20
21use 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;
36
37use 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};
47
48pub struct SwapCallDebris {
49    pub params: MoneyTransferParamsV1,
50    pub proofs: Vec<Proof>,
51    pub signature_secret: SecretKey,
52}
53
54/// 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
59    pub pubkey: PublicKey,
60    /// The value of the party's input to swap (send)
61    pub value_send: u64,
62    /// The token ID of the party's input to swap (send)
63    pub token_id_send: TokenId,
64    /// The value of the party's output to receive
65    pub value_recv: u64,
66    /// The token ID of the party's output to receive
67    pub token_id_recv: TokenId,
68    /// User data blind for the party's input
69    pub user_data_blind_send: BaseBlind,
70    /// Spend hook for the party's output
71    pub spend_hook_recv: FuncId,
72    /// User data for the party's output
73    pub 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`.
78    pub value_blinds: [ScalarBlind; 2],
79    /// The blinds to be used for token ID pedersen commitments
80    pub token_blinds: [BaseBlind; 2],
81    /// The coin to be used as the input to the swap
82    pub coin: OwnCoin,
83    /// Merkle tree of coins used to create inclusion proofs
84    pub tree: MerkleTree,
85    /// `Mint_V1` zkas circuit ZkBinary
86    pub mint_zkbin: ZkBinary,
87    /// Proving key for the `Mint_V1` zk circuit
88    pub mint_pk: ProvingKey,
89    /// `Burn_V1` zkas circuit ZkBinary
90    pub burn_zkbin: ZkBinary,
91    /// Proving key for the `Burn_V1` zk circuit
92    pub burn_pk: ProvingKey,
93}
94
95impl SwapCallBuilder {
96    pub fn build(&self) -> Result<SwapCallDebris> {
97        debug!(target: "contract::money::client::swap", "Building half of Money::OtcSwapV1 contract call");
98        if self.value_send == 0 {
99            error!(target: "contract::money::client::swap", "Error: Value send is 0");
100            return Err(ClientFailed::InvalidAmount(self.value_send).into())
101        }
102
103        if self.value_recv == 0 {
104            error!(target: "contract::money::client::swap", "Error: Value receive is 0");
105            return Err(ClientFailed::InvalidAmount(self.value_recv).into())
106        }
107
108        if self.token_id_send.inner() == pallas::Base::ZERO {
109            error!(target: "contract::money::client::swap", "Error: Token send is ZERO");
110            return Err(ClientFailed::InvalidTokenId(self.token_id_send.to_string()).into())
111        }
112
113        if self.token_id_recv.inner() == pallas::Base::ZERO {
114            error!(target: "contract::money::client::swap", "Error: Token receive is ZERO");
115            return Err(ClientFailed::InvalidTokenId(self.token_id_recv.to_string()).into())
116        }
117
118        if self.coin.note.value != self.value_send {
119            return Err(ClientFailed::InvalidAmount(self.coin.note.value).into())
120        }
121
122        if self.coin.note.token_id != self.token_id_send {
123            return Err(ClientFailed::InvalidTokenId(self.coin.note.token_id.to_string()).into())
124        }
125
126        let 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        };
131
132        let 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        };
140
141        // Now we fill this with necessary stuff
142        let mut params = MoneyTransferParamsV1 { inputs: vec![], outputs: vec![] };
143
144        // Create a new ephemeral secret key
145        let signature_secret = SecretKey::random(&mut OsRng);
146
147        let mut proofs = vec![];
148        debug!(target: "contract::money::client::swap", "Creating burn proof for input");
149        let (proof, public_inputs) = create_transfer_burn_proof(
150            &self.burn_zkbin,
151            &self.burn_pk,
152            &input,
153            self.value_blinds[0],
154            self.token_blinds[0],
155            signature_secret,
156        )?;
157
158        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        });
166
167        proofs.push(proof);
168
169        // For the output, we create a new coin blind
170        let coin_blind = Blind::random(&mut OsRng);
171
172        debug!(target: "contract::money::client::swap", "Creating mint proof for output");
173        let (proof, public_inputs) = create_transfer_mint_proof(
174            &self.mint_zkbin,
175            &self.mint_pk,
176            &output,
177            self.value_blinds[1],
178            self.token_blinds[1],
179            self.spend_hook_recv,
180            self.user_data_recv,
181            coin_blind,
182        )?;
183
184        proofs.push(proof);
185
186        // Encrypted note
187        let 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
196            memo: serialize(&signature_secret),
197        };
198
199        let encrypted_note = AeadEncryptedNote::encrypt(&note, &self.pubkey, &mut OsRng)?;
200
201        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        });
207
208        // Now we should have all the params, zk proofs, and signature secrets.
209        // We return it all and let the caller deal with it.
210        let debris = SwapCallDebris { params, proofs, signature_secret };
211        Ok(debris)
212    }
213}