darkfi_money_contract/client/
genesis_mint_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
19use darkfi::{
20    zk::{Proof, ProvingKey},
21    zkas::ZkBinary,
22    ClientFailed, Result,
23};
24use darkfi_sdk::{
25    crypto::{note::AeadEncryptedNote, pasta_prelude::*, Blind, FuncId, PublicKey},
26    pasta::pallas,
27};
28use log::debug;
29use rand::{prelude::SliceRandom, rngs::OsRng};
30
31use crate::{
32    client::{
33        compute_remainder_blind,
34        transfer_v1::{proof::create_transfer_mint_proof, TransferCallOutput},
35        MoneyNote,
36    },
37    model::{ClearInput, Coin, MoneyGenesisMintParamsV1, Output, DARK_TOKEN_ID},
38};
39
40pub struct GenesisMintCallDebris {
41    pub params: MoneyGenesisMintParamsV1,
42    pub proofs: Vec<Proof>,
43}
44
45pub struct GenesisMintRevealed {
46    pub coin: Coin,
47    pub value_commit: pallas::Point,
48    pub token_commit: pallas::Base,
49}
50
51impl GenesisMintRevealed {
52    pub fn to_vec(&self) -> Vec<pallas::Base> {
53        let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
54
55        // NOTE: It's important to keep these in the same order
56        // as the `constrain_instance` calls in the zkas code.
57        vec![self.coin.inner(), *valcom_coords.x(), *valcom_coords.y(), self.token_commit]
58    }
59}
60
61/// Struct holding necessary information to build a `Money::GenesisMintV1` contract call.
62pub struct GenesisMintCallBuilder {
63    /// Caller's public key, corresponding to the one used in the signature
64    pub signature_public: PublicKey,
65    /// Vector containing each output value we want to mint
66    pub amounts: Vec<u64>,
67    /// Optional recipient's public key, in case we want to mint to a different address
68    pub recipient: Option<PublicKey>,
69    /// Optional contract spend hook to use in the output
70    pub spend_hook: Option<FuncId>,
71    /// Optional user data to use in the output
72    pub user_data: Option<pallas::Base>,
73    /// `Mint_V1` zkas circuit ZkBinary
74    pub mint_zkbin: ZkBinary,
75    /// Proving key for the `Mint_V1` zk circuit
76    pub mint_pk: ProvingKey,
77}
78
79impl GenesisMintCallBuilder {
80    pub fn build(&self) -> Result<GenesisMintCallDebris> {
81        debug!(target: "contract::money::client::genesis_mint", "Building Money::MintV1 contract call");
82        let value = self.amounts.iter().sum();
83        if value == 0 {
84            return Err(ClientFailed::InvalidAmount(value).into())
85        }
86
87        // In this call, we will build one clear input and one anonymous output.
88        // Only DARK_TOKEN_ID can be minted on genesis block.
89        let token_id = *DARK_TOKEN_ID;
90
91        // Building the clear input using random blinds
92        let value_blind = Blind::random(&mut OsRng);
93        let token_blind = Blind::random(&mut OsRng);
94        let input = ClearInput {
95            value,
96            token_id,
97            value_blind,
98            token_blind,
99            signature_public: self.signature_public,
100        };
101
102        // Grab the public key, spend hook and user data to use in the outputs
103        let public_key = self.recipient.unwrap_or(self.signature_public);
104        let spend_hook = self.spend_hook.unwrap_or(FuncId::none());
105        let user_data = self.user_data.unwrap_or(pallas::Base::ZERO);
106
107        // Shuffle the amounts vector so our outputs are not in order
108        let mut amounts = self.amounts.clone();
109        amounts.shuffle(&mut OsRng);
110
111        // Building the anonymous outputs
112        let input_blinds = vec![value_blind];
113        let mut output_blinds = Vec::with_capacity(amounts.len());
114        let mut outputs = Vec::with_capacity(amounts.len());
115        let mut proofs = Vec::with_capacity(amounts.len());
116        for (i, amount) in amounts.iter().enumerate() {
117            let value_blind = if i == amounts.len() - 1 {
118                compute_remainder_blind(&input_blinds, &output_blinds)
119            } else {
120                Blind::random(&mut OsRng)
121            };
122            output_blinds.push(value_blind);
123
124            let output = TransferCallOutput {
125                public_key,
126                value: *amount,
127                token_id,
128                spend_hook,
129                user_data,
130                blind: Blind::random(&mut OsRng),
131            };
132
133            debug!(target: "contract::money::client::genesis_mint", "Creating token mint proof for output {}", i);
134            let (proof, public_inputs) = create_transfer_mint_proof(
135                &self.mint_zkbin,
136                &self.mint_pk,
137                &output,
138                value_blind,
139                token_blind,
140                spend_hook,
141                user_data,
142                output.blind,
143            )?;
144            proofs.push(proof);
145
146            let note = MoneyNote {
147                value: output.value,
148                token_id: output.token_id,
149                spend_hook,
150                user_data,
151                coin_blind: output.blind,
152                value_blind,
153                token_blind,
154                memo: vec![],
155            };
156
157            let encrypted_note = AeadEncryptedNote::encrypt(&note, &public_key, &mut OsRng)?;
158
159            let output = Output {
160                value_commit: public_inputs.value_commit,
161                token_commit: public_inputs.token_commit,
162                coin: public_inputs.coin,
163                note: encrypted_note,
164            };
165
166            outputs.push(output);
167        }
168
169        let params = MoneyGenesisMintParamsV1 { input, outputs };
170        let debris = GenesisMintCallDebris { params, proofs };
171        Ok(debris)
172    }
173}