darkfi_money_contract/client/
burn_v1.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 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
19use darkfi::{
20    zk::{Proof, ProvingKey},
21    zkas::ZkBinary,
22    ClientFailed, Result,
23};
24use darkfi_sdk::crypto::{BaseBlind, Blind, MerkleTree, ScalarBlind, SecretKey};
25use rand::rngs::OsRng;
26use tracing::debug;
27
28use crate::{
29    client::{
30        transfer_v1::{proof::create_transfer_burn_proof, TransferCallInput},
31        OwnCoin,
32    },
33    error::MoneyError,
34    model::{Input, MoneyBurnParamsV1},
35};
36
37/// Struct holding necessary information to build a `Money::BurnV1`
38/// contract call.
39pub struct BurnCallBuilder {
40    /// Anonymous inputs
41    pub inputs: Vec<TransferCallInput>,
42    /// `Burn_V1` zkas circuit ZkBinary
43    pub burn_zkbin: ZkBinary,
44    /// Proving key for the `Burn_V1` zk circuit
45    pub burn_pk: ProvingKey,
46}
47
48impl BurnCallBuilder {
49    pub fn build(self) -> Result<(MoneyBurnParamsV1, BurnCallDebris)> {
50        debug!(target: "contract::money::client::burn::build", "Building Money::BurnV1 contract call");
51        if self.inputs.is_empty() {
52            return Err(ClientFailed::VerifyError(MoneyError::BurnMissingInputs.to_string()).into())
53        }
54
55        let mut params = MoneyBurnParamsV1 { inputs: vec![] };
56        let mut signature_secrets = vec![];
57        let mut proofs = vec![];
58
59        let token_blind = BaseBlind::random(&mut OsRng);
60        let mut input_value_blinds = vec![];
61
62        debug!(target: "contract::money::client::burn::build", "Building anonymous inputs");
63        for (i, input) in self.inputs.iter().enumerate() {
64            let value_blind = Blind::random(&mut OsRng);
65            input_value_blinds.push(value_blind);
66
67            let signature_secret = SecretKey::random(&mut OsRng);
68            signature_secrets.push(signature_secret);
69
70            debug!(target: "contract::money::client::burn::build", "Creating burn proof for input {i}");
71            let (proof, public_inputs) = create_transfer_burn_proof(
72                &self.burn_zkbin,
73                &self.burn_pk,
74                input,
75                value_blind,
76                token_blind,
77                signature_secret,
78            )?;
79
80            params.inputs.push(Input {
81                value_commit: public_inputs.value_commit,
82                token_commit: public_inputs.token_commit,
83                nullifier: public_inputs.nullifier,
84                merkle_root: public_inputs.merkle_root,
85                user_data_enc: public_inputs.user_data_enc,
86                signature_public: public_inputs.signature_public,
87                tx_local: false,
88            });
89
90            proofs.push(proof);
91        }
92
93        let secrets = BurnCallDebris { proofs, signature_secrets, input_value_blinds, token_blind };
94        Ok((params, secrets))
95    }
96}
97
98pub struct BurnCallDebris {
99    /// The ZK proofs created in this builder
100    pub proofs: Vec<Proof>,
101    /// The ephemeral secret keys created for signing
102    pub signature_secrets: Vec<SecretKey>,
103    /// The value blinds created for each input
104    pub input_value_blinds: Vec<ScalarBlind>,
105    /// The token blind used for all inputs
106    pub token_blind: BaseBlind,
107}
108
109/// Make a simple burn call to permanently destroy coins.
110///
111/// * `coins`: Set of `OwnCoin` we're given to burn in this call
112/// * `tree`: Merkle tree of coins used to create inclusion proofs
113/// * `burn_zkbin`: `Burn_V1` zkas circuit ZkBinary
114/// * `burn_pk`: Proving key for the `Burn_V1` zk circuit
115///
116/// Returns a tuple of:
117///
118/// * The actual call data
119/// * Secret values such as blinds
120/// * A list of the spent coins
121pub fn make_burn_call(
122    coins: Vec<OwnCoin>,
123    tree: MerkleTree,
124    burn_zkbin: ZkBinary,
125    burn_pk: ProvingKey,
126) -> Result<(MoneyBurnParamsV1, BurnCallDebris, Vec<OwnCoin>)> {
127    debug!(target: "contract::money::client::burn", "Building Money::BurnV1 contract call");
128
129    if coins.is_empty() {
130        return Err(ClientFailed::VerifyError(MoneyError::BurnMissingInputs.to_string()).into())
131    }
132
133    // Ensure the coins given to us are all of the same token ID.
134    let token_id = coins[0].note.token_id;
135    for coin in &coins {
136        if coin.note.token_id != token_id {
137            return Err(ClientFailed::InvalidTokenId(coin.note.token_id.to_string()).into())
138        }
139    }
140
141    let mut inputs = vec![];
142    for coin in coins.iter() {
143        let input = TransferCallInput {
144            coin: coin.clone(),
145            merkle_path: tree.witness(coin.leaf_position, 0).unwrap(),
146            user_data_blind: Blind::random(&mut OsRng),
147        };
148
149        inputs.push(input);
150    }
151
152    let burn_builder = BurnCallBuilder { inputs, burn_zkbin, burn_pk };
153
154    let (params, secrets) = burn_builder.build()?;
155
156    Ok((params, secrets, coins))
157}