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 */
1819use 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};
3031use 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};
3940pub struct GenesisMintCallDebris {
41pub params: MoneyGenesisMintParamsV1,
42pub proofs: Vec<Proof>,
43}
4445pub struct GenesisMintRevealed {
46pub coin: Coin,
47pub value_commit: pallas::Point,
48pub token_commit: pallas::Base,
49}
5051impl GenesisMintRevealed {
52pub fn to_vec(&self) -> Vec<pallas::Base> {
53let valcom_coords = self.value_commit.to_affine().coordinates().unwrap();
5455// NOTE: It's important to keep these in the same order
56 // as the `constrain_instance` calls in the zkas code.
57vec![self.coin.inner(), *valcom_coords.x(), *valcom_coords.y(), self.token_commit]
58 }
59}
6061/// 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
64pub signature_public: PublicKey,
65/// Vector containing each output value we want to mint
66pub amounts: Vec<u64>,
67/// Optional recipient's public key, in case we want to mint to a different address
68pub recipient: Option<PublicKey>,
69/// Optional contract spend hook to use in the output
70pub spend_hook: Option<FuncId>,
71/// Optional user data to use in the output
72pub user_data: Option<pallas::Base>,
73/// `Mint_V1` zkas circuit ZkBinary
74pub mint_zkbin: ZkBinary,
75/// Proving key for the `Mint_V1` zk circuit
76pub mint_pk: ProvingKey,
77}
7879impl GenesisMintCallBuilder {
80pub fn build(&self) -> Result<GenesisMintCallDebris> {
81debug!(target: "contract::money::client::genesis_mint", "Building Money::MintV1 contract call");
82let value = self.amounts.iter().sum();
83if value == 0 {
84return Err(ClientFailed::InvalidAmount(value).into())
85 }
8687// In this call, we will build one clear input and one anonymous output.
88 // Only DARK_TOKEN_ID can be minted on genesis block.
89let token_id = *DARK_TOKEN_ID;
9091// Building the clear input using random blinds
92let value_blind = Blind::random(&mut OsRng);
93let token_blind = Blind::random(&mut OsRng);
94let input = ClearInput {
95 value,
96 token_id,
97 value_blind,
98 token_blind,
99 signature_public: self.signature_public,
100 };
101102// Grab the public key, spend hook and user data to use in the outputs
103let public_key = self.recipient.unwrap_or(self.signature_public);
104let spend_hook = self.spend_hook.unwrap_or(FuncId::none());
105let user_data = self.user_data.unwrap_or(pallas::Base::ZERO);
106107// Shuffle the amounts vector so our outputs are not in order
108let mut amounts = self.amounts.clone();
109 amounts.shuffle(&mut OsRng);
110111// Building the anonymous outputs
112let input_blinds = vec![value_blind];
113let mut output_blinds = Vec::with_capacity(amounts.len());
114let mut outputs = Vec::with_capacity(amounts.len());
115let mut proofs = Vec::with_capacity(amounts.len());
116for (i, amount) in amounts.iter().enumerate() {
117let 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);
123124let output = TransferCallOutput {
125 public_key,
126 value: *amount,
127 token_id,
128 spend_hook,
129 user_data,
130 blind: Blind::random(&mut OsRng),
131 };
132133debug!(target: "contract::money::client::genesis_mint", "Creating token mint proof for output {}", i);
134let (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);
145146let 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 };
156157let encrypted_note = AeadEncryptedNote::encrypt(¬e, &public_key, &mut OsRng)?;
158159let 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 };
165166 outputs.push(output);
167 }
168169let params = MoneyGenesisMintParamsV1 { input, outputs };
170let debris = GenesisMintCallDebris { params, proofs };
171Ok(debris)
172 }
173}