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            });
88
89            proofs.push(proof);
90        }
91
92        let secrets = BurnCallDebris { proofs, signature_secrets, input_value_blinds, token_blind };
93        Ok((params, secrets))
94    }
95}
96
97pub struct BurnCallDebris {
98    /// The ZK proofs created in this builder
99    pub proofs: Vec<Proof>,
100    /// The ephemeral secret keys created for signing
101    pub signature_secrets: Vec<SecretKey>,
102    /// The value blinds created for each input
103    pub input_value_blinds: Vec<ScalarBlind>,
104    /// The token blind used for all inputs
105    pub token_blind: BaseBlind,
106}
107
108/// Make a simple burn call to permanently destroy coins.
109///
110/// * `coins`: Set of `OwnCoin` we're given to burn in this call
111/// * `tree`: Merkle tree of coins used to create inclusion proofs
112/// * `burn_zkbin`: `Burn_V1` zkas circuit ZkBinary
113/// * `burn_pk`: Proving key for the `Burn_V1` zk circuit
114///
115/// Returns a tuple of:
116///
117/// * The actual call data
118/// * Secret values such as blinds
119/// * A list of the spent coins
120pub fn make_burn_call(
121    coins: Vec<OwnCoin>,
122    tree: MerkleTree,
123    burn_zkbin: ZkBinary,
124    burn_pk: ProvingKey,
125) -> Result<(MoneyBurnParamsV1, BurnCallDebris, Vec<OwnCoin>)> {
126    debug!(target: "contract::money::client::burn", "Building Money::BurnV1 contract call");
127
128    if coins.is_empty() {
129        return Err(ClientFailed::VerifyError(MoneyError::BurnMissingInputs.to_string()).into())
130    }
131
132    // Ensure the coins given to us are all of the same token ID.
133    let token_id = coins[0].note.token_id;
134    for coin in &coins {
135        if coin.note.token_id != token_id {
136            return Err(ClientFailed::InvalidTokenId(coin.note.token_id.to_string()).into())
137        }
138    }
139
140    let mut inputs = vec![];
141    for coin in coins.iter() {
142        let input = TransferCallInput {
143            coin: coin.clone(),
144            merkle_path: tree.witness(coin.leaf_position, 0).unwrap(),
145            user_data_blind: Blind::random(&mut OsRng),
146        };
147
148        inputs.push(input);
149    }
150
151    let burn_builder = BurnCallBuilder { inputs, burn_zkbin, burn_pk };
152
153    let (params, secrets) = burn_builder.build()?;
154
155    Ok((params, secrets, coins))
156}