darkfi_money_contract/client/transfer_v1/
builder.rs1use darkfi::{
20 zk::{Proof, ProvingKey},
21 zkas::ZkBinary,
22 ClientFailed, Result,
23};
24use darkfi_sdk::{
25 crypto::{
26 note::AeadEncryptedNote, pasta_prelude::*, BaseBlind, Blind, MerkleNode, ScalarBlind,
27 SecretKey,
28 },
29 pasta::pallas,
30};
31use rand::rngs::OsRng;
32use tracing::debug;
33
34use super::proof::{create_transfer_burn_proof, create_transfer_mint_proof};
35use crate::{
36 client::{compute_remainder_blind, MoneyNote, OwnCoin, TokenId},
37 error::MoneyError,
38 model::{CoinAttributes, Input, MoneyTransferParamsV1, Output},
39};
40
41pub struct TransferCallBuilder {
43 pub clear_inputs: Vec<TransferCallClearInput>,
45 pub inputs: Vec<TransferCallInput>,
47 pub outputs: Vec<TransferCallOutput>,
49 pub mint_zkbin: ZkBinary,
51 pub mint_pk: ProvingKey,
53 pub burn_zkbin: ZkBinary,
55 pub burn_pk: ProvingKey,
57}
58
59pub struct TransferCallClearInput {
60 pub value: u64,
61 pub token_id: TokenId,
62 pub signature_secret: SecretKey,
63}
64
65pub struct TransferCallInput {
66 pub coin: OwnCoin,
68 pub merkle_path: Vec<MerkleNode>,
70 pub user_data_blind: BaseBlind,
73}
74
75pub type TransferCallOutput = CoinAttributes;
76
77impl TransferCallBuilder {
78 pub fn build(self) -> Result<(MoneyTransferParamsV1, TransferCallSecrets)> {
79 debug!(target: "contract::money::client::transfer::build", "Building Money::TransferV1 contract call");
80 if self.clear_inputs.is_empty() && self.inputs.is_empty() {
81 return Err(
82 ClientFailed::VerifyError(MoneyError::TransferMissingInputs.to_string()).into()
83 )
84 }
85
86 let mut params = MoneyTransferParamsV1 { inputs: vec![], outputs: vec![] };
87 let mut signature_secrets = vec![];
88 let mut proofs = vec![];
89
90 let token_blind = BaseBlind::random(&mut OsRng);
91 let mut input_blinds = vec![];
92 let mut output_blinds = vec![];
93
94 debug!(target: "contract::money::client::transfer::build", "Building anonymous inputs");
95 for (i, input) in self.inputs.iter().enumerate() {
96 let value_blind = Blind::random(&mut OsRng);
97 input_blinds.push(value_blind);
98
99 let signature_secret = SecretKey::random(&mut OsRng);
100 signature_secrets.push(signature_secret);
101
102 debug!(target: "contract::money::client::transfer::build", "Creating transfer burn proof for input {i}");
103 let (proof, public_inputs) = create_transfer_burn_proof(
104 &self.burn_zkbin,
105 &self.burn_pk,
106 input,
107 value_blind,
108 token_blind,
109 signature_secret,
110 )?;
111
112 params.inputs.push(Input {
113 value_commit: public_inputs.value_commit,
114 token_commit: public_inputs.token_commit,
115 nullifier: public_inputs.nullifier,
116 merkle_root: public_inputs.merkle_root,
117 user_data_enc: public_inputs.user_data_enc,
118 signature_public: public_inputs.signature_public,
119 tx_local: false,
120 });
121
122 proofs.push(proof);
123 }
124
125 if self.outputs.is_empty() {
127 return Err(
128 ClientFailed::VerifyError(MoneyError::TransferMissingOutputs.to_string()).into()
129 )
130 }
131
132 let mut output_notes = vec![];
133
134 for (i, output) in self.outputs.iter().enumerate() {
135 let value_blind = if i == self.outputs.len() - 1 {
136 compute_remainder_blind(&input_blinds, &output_blinds)
137 } else {
138 Blind::random(&mut OsRng)
139 };
140
141 output_blinds.push(value_blind);
142
143 debug!(target: "contract::money::client::transfer::build", "Creating transfer mint proof for output {i}");
144 let (proof, public_inputs) = create_transfer_mint_proof(
145 &self.mint_zkbin,
146 &self.mint_pk,
147 output,
148 value_blind,
149 token_blind,
150 output.spend_hook,
151 output.user_data,
152 output.blind,
153 )?;
154
155 proofs.push(proof);
156
157 let note = MoneyNote {
159 value: output.value,
160 token_id: output.token_id,
161 spend_hook: output.spend_hook,
162 user_data: output.user_data,
163 coin_blind: output.blind,
164 value_blind,
165 token_blind,
166 memo: vec![],
167 };
168
169 let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
170 output_notes.push(note);
171
172 params.outputs.push(Output {
173 value_commit: public_inputs.value_commit,
174 token_commit: public_inputs.token_commit,
175 coin: public_inputs.coin,
176 note: encrypted_note,
177 tx_local: false,
178 });
179 }
180
181 let secrets = TransferCallSecrets {
184 proofs,
185 signature_secrets,
186 output_notes,
187 input_value_blinds: input_blinds,
188 output_value_blinds: output_blinds,
189 };
190 Ok((params, secrets))
191 }
192}
193
194pub struct TransferCallSecrets {
195 pub proofs: Vec<Proof>,
197 pub signature_secrets: Vec<SecretKey>,
199
200 pub output_notes: Vec<MoneyNote>,
202
203 pub input_value_blinds: Vec<ScalarBlind>,
205 pub output_value_blinds: Vec<ScalarBlind>,
207}
208
209impl TransferCallSecrets {
210 pub fn minted_coins(&self, params: &MoneyTransferParamsV1) -> Vec<OwnCoin> {
211 let mut minted_coins = vec![];
212 for (output, output_note) in params.outputs.iter().zip(self.output_notes.iter()) {
213 minted_coins.push(OwnCoin {
214 coin: output.coin,
215 note: output_note.clone(),
216 secret: SecretKey::from(pallas::Base::ZERO),
217 leaf_position: 0.into(),
218 });
219 }
220 minted_coins
221 }
222}