darkfi_money_contract/client/transfer_v1/
builder.rs
1use 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 log::debug;
32use rand::rngs::OsRng;
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 });
120
121 proofs.push(proof);
122 }
123
124 if self.outputs.is_empty() {
126 return Err(
127 ClientFailed::VerifyError(MoneyError::TransferMissingOutputs.to_string()).into()
128 )
129 }
130
131 let mut output_notes = vec![];
132
133 for (i, output) in self.outputs.iter().enumerate() {
134 let value_blind = if i == self.outputs.len() - 1 {
135 compute_remainder_blind(&input_blinds, &output_blinds)
136 } else {
137 Blind::random(&mut OsRng)
138 };
139
140 output_blinds.push(value_blind);
141
142 debug!(target: "contract::money::client::transfer::build", "Creating transfer mint proof for output {}", i);
143 let (proof, public_inputs) = create_transfer_mint_proof(
144 &self.mint_zkbin,
145 &self.mint_pk,
146 output,
147 value_blind,
148 token_blind,
149 output.spend_hook,
150 output.user_data,
151 output.blind,
152 )?;
153
154 proofs.push(proof);
155
156 let note = MoneyNote {
158 value: output.value,
159 token_id: output.token_id,
160 spend_hook: output.spend_hook,
161 user_data: output.user_data,
162 coin_blind: output.blind,
163 value_blind,
164 token_blind,
165 memo: vec![],
166 };
167
168 let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
169 output_notes.push(note);
170
171 params.outputs.push(Output {
172 value_commit: public_inputs.value_commit,
173 token_commit: public_inputs.token_commit,
174 coin: public_inputs.coin,
175 note: encrypted_note,
176 });
177 }
178
179 let secrets = TransferCallSecrets {
182 proofs,
183 signature_secrets,
184 output_notes,
185 input_value_blinds: input_blinds,
186 output_value_blinds: output_blinds,
187 };
188 Ok((params, secrets))
189 }
190}
191
192pub struct TransferCallSecrets {
193 pub proofs: Vec<Proof>,
195 pub signature_secrets: Vec<SecretKey>,
197
198 pub output_notes: Vec<MoneyNote>,
200
201 pub input_value_blinds: Vec<ScalarBlind>,
203 pub output_value_blinds: Vec<ScalarBlind>,
205}
206
207impl TransferCallSecrets {
208 pub fn minted_coins(&self, params: &MoneyTransferParamsV1) -> Vec<OwnCoin> {
209 let mut minted_coins = vec![];
210 for (output, output_note) in params.outputs.iter().zip(self.output_notes.iter()) {
211 minted_coins.push(OwnCoin {
212 coin: output.coin,
213 note: output_note.clone(),
214 secret: SecretKey::from(pallas::Base::ZERO),
215 leaf_position: 0.into(),
216 });
217 }
218 minted_coins
219 }
220}