1use rand::rngs::OsRng;
20use rusqlite::types::Value;
21
22use darkfi::{
23 tx::{ContractCallLeaf, Transaction, TransactionBuilder},
24 util::parse::decode_base10,
25 zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
26 zkas::ZkBinary,
27 Error, Result,
28};
29use darkfi_money_contract::{
30 client::{
31 auth_token_freeze_v1::AuthTokenFreezeCallBuilder,
32 auth_token_mint_v1::AuthTokenMintCallBuilder, token_mint_v1::TokenMintCallBuilder,
33 },
34 model::{CoinAttributes, TokenAttributes, TokenId},
35 MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
36 MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
37};
38use darkfi_sdk::{
39 crypto::{
40 contract_id::MONEY_CONTRACT_ID, poseidon_hash, BaseBlind, Blind, FuncId, FuncRef, Keypair,
41 PublicKey, SecretKey,
42 },
43 dark_tree::DarkTree,
44 pasta::pallas,
45 tx::ContractCall,
46};
47use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
48
49use crate::{
50 convert_named_params,
51 error::WalletDbResult,
52 money::{
53 BALANCE_BASE10_DECIMALS, MONEY_TOKENS_COL_FREEZE_HEIGHT, MONEY_TOKENS_COL_IS_FROZEN,
54 MONEY_TOKENS_COL_MINT_AUTHORITY, MONEY_TOKENS_COL_TOKEN_BLIND, MONEY_TOKENS_COL_TOKEN_ID,
55 MONEY_TOKENS_TABLE,
56 },
57 Drk,
58};
59
60impl Drk {
61 fn derive_token_attributes(
63 &self,
64 mint_authority: SecretKey,
65 token_blind: BaseBlind,
66 ) -> TokenAttributes {
67 let auth_func_id = FuncRef {
69 contract_id: *MONEY_CONTRACT_ID,
70 func_code: MoneyFunction::AuthTokenMintV1 as u8,
71 }
72 .to_func_id();
73
74 let (mint_auth_x, mint_auth_y) = PublicKey::from_secret(mint_authority).xy();
76
77 TokenAttributes {
79 auth_parent: auth_func_id,
80 user_data: poseidon_hash([mint_auth_x, mint_auth_y]),
81 blind: token_blind,
82 }
83 }
84
85 pub async fn import_mint_authority(
87 &self,
88 mint_authority: SecretKey,
89 token_blind: BaseBlind,
90 ) -> Result<TokenId> {
91 let token_id = self.derive_token_attributes(mint_authority, token_blind).to_token_id();
92 let is_frozen = 0;
93 let freeze_height: Option<u32> = None;
94
95 let query = format!(
96 "INSERT INTO {} ({}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5);",
97 *MONEY_TOKENS_TABLE,
98 MONEY_TOKENS_COL_TOKEN_ID,
99 MONEY_TOKENS_COL_MINT_AUTHORITY,
100 MONEY_TOKENS_COL_TOKEN_BLIND,
101 MONEY_TOKENS_COL_IS_FROZEN,
102 MONEY_TOKENS_COL_FREEZE_HEIGHT,
103 );
104
105 if let Err(e) = self.wallet.exec_sql(
106 &query,
107 rusqlite::params![
108 serialize_async(&token_id).await,
109 serialize_async(&mint_authority).await,
110 serialize_async(&token_blind).await,
111 is_frozen,
112 freeze_height,
113 ],
114 ) {
115 return Err(Error::DatabaseError(format!(
116 "[import_mint_authority] Inserting mint authority failed: {e}"
117 )))
118 };
119
120 Ok(token_id)
121 }
122
123 async fn parse_mint_authority_record(
126 &self,
127 row: &[Value],
128 ) -> Result<(TokenId, SecretKey, BaseBlind, bool, Option<u32>)> {
129 let Value::Blob(ref token_bytes) = row[0] else {
130 return Err(Error::ParseFailed(
131 "[parse_mint_authority_record] Token ID bytes parsing failed",
132 ))
133 };
134 let token_id = deserialize_async(token_bytes).await?;
135
136 let Value::Blob(ref auth_bytes) = row[1] else {
137 return Err(Error::ParseFailed(
138 "[parse_mint_authority_record] Mint authority bytes parsing failed",
139 ))
140 };
141 let mint_authority = deserialize_async(auth_bytes).await?;
142
143 let Value::Blob(ref token_blind_bytes) = row[2] else {
144 return Err(Error::ParseFailed(
145 "[parse_mint_authority_record] Token blind bytes parsing failed",
146 ))
147 };
148 let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
149
150 let Value::Integer(frozen) = row[3] else {
151 return Err(Error::ParseFailed("[parse_mint_authority_record] Is frozen parsing failed"))
152 };
153 let Ok(frozen) = i32::try_from(frozen) else {
154 return Err(Error::ParseFailed("[parse_mint_authority_record] Is frozen parsing failed"))
155 };
156
157 let freeze_height = match row[4] {
158 Value::Integer(freeze_height) => {
159 let Ok(freeze_height) = u32::try_from(freeze_height) else {
160 return Err(Error::ParseFailed(
161 "[parse_mint_authority_record] Freeze height parsing failed",
162 ))
163 };
164 Some(freeze_height)
165 }
166 Value::Null => None,
167 _ => {
168 return Err(Error::ParseFailed(
169 "[parse_mint_authority_record] Freeze height parsing failed",
170 ))
171 }
172 };
173
174 Ok((token_id, mint_authority, token_blind, frozen != 0, freeze_height))
175 }
176
177 pub fn reset_mint_authorities(&self, output: &mut Vec<String>) -> WalletDbResult<()> {
179 output.push(String::from("Resetting mint authorities frozen status"));
180 let query = format!(
181 "UPDATE {} SET {} = 0, {} = NULL;",
182 *MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_FREEZE_HEIGHT
183 );
184 self.wallet.exec_sql(&query, &[])?;
185 output.push(String::from("Successfully reset mint authorities frozen status"));
186
187 Ok(())
188 }
189
190 pub fn unfreeze_mint_authorities_after(
193 &self,
194 height: &u32,
195 output: &mut Vec<String>,
196 ) -> WalletDbResult<()> {
197 output.push(format!("Resetting mint authorities frozen status after: {height}"));
198 let query = format!(
199 "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;",
200 *MONEY_TOKENS_TABLE,
201 MONEY_TOKENS_COL_IS_FROZEN,
202 MONEY_TOKENS_COL_FREEZE_HEIGHT,
203 MONEY_TOKENS_COL_FREEZE_HEIGHT
204 );
205 self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?;
206 output.push(String::from("Successfully reset mint authorities frozen status"));
207
208 Ok(())
209 }
210
211 pub async fn get_mint_authorities(
213 &self,
214 ) -> Result<Vec<(TokenId, SecretKey, BaseBlind, bool, Option<u32>)>> {
215 let rows = match self.wallet.query_multiple(&MONEY_TOKENS_TABLE, &[], &[]) {
216 Ok(r) => r,
217 Err(e) => {
218 return Err(Error::DatabaseError(format!(
219 "[get_mint_authorities] Tokens mint autorities retrieval failed: {e}"
220 )))
221 }
222 };
223
224 let mut ret = Vec::with_capacity(rows.len());
225 for row in rows {
226 ret.push(self.parse_mint_authority_record(&row).await?);
227 }
228
229 Ok(ret)
230 }
231
232 async fn get_token_mint_authority(
234 &self,
235 token_id: &TokenId,
236 ) -> Result<(TokenId, SecretKey, BaseBlind, bool, Option<u32>)> {
237 let row = match self.wallet.query_single(
238 &MONEY_TOKENS_TABLE,
239 &[],
240 convert_named_params! {(MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await)},
241 ) {
242 Ok(r) => r,
243 Err(e) => {
244 return Err(Error::DatabaseError(format!(
245 "[get_token_mint_authority] Token mint autority retrieval failed: {e}"
246 )))
247 }
248 };
249
250 let token = self.parse_mint_authority_record(&row).await?;
251
252 if token.3 {
253 return Err(Error::Custom(
254 "This token mint is marked as frozen in the wallet".to_string(),
255 ))
256 }
257
258 Ok(token)
259 }
260
261 pub async fn mint_token(
263 &self,
264 amount: &str,
265 recipient: PublicKey,
266 token_id: TokenId,
267 spend_hook: Option<FuncId>,
268 user_data: Option<pallas::Base>,
269 ) -> Result<Transaction> {
270 let amount = decode_base10(amount, BALANCE_BASE10_DECIMALS, false)?;
272
273 let token_mint_authority = self.get_token_mint_authority(&token_id).await?;
275 let token_attrs =
276 self.derive_token_attributes(token_mint_authority.1, token_mint_authority.2);
277 let mint_authority = Keypair::new(token_mint_authority.1);
278
279 assert_eq!(token_id, token_attrs.to_token_id());
281
282 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
286
287 let Some(mint_zkbin) =
288 zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1)
289 else {
290 return Err(Error::Custom("Token mint circuit not found".to_string()))
291 };
292
293 let Some(auth_mint_zkbin) =
294 zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1)
295 else {
296 return Err(Error::Custom("Auth token mint circuit not found".to_string()))
297 };
298
299 let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
300 else {
301 return Err(Error::Custom("Fee circuit not found".to_string()))
302 };
303
304 let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
305 let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1)?;
306 let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
307
308 let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
309 let auth_mint_circuit =
310 ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
311 let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
312
313 let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
315 let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &auth_mint_circuit);
316 let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
317
318 let coin_attrs = CoinAttributes {
320 public_key: recipient,
321 value: amount,
322 token_id,
323 spend_hook: spend_hook.unwrap_or(FuncId::none()),
324 user_data: user_data.unwrap_or(pallas::Base::ZERO),
325 blind: Blind::random(&mut OsRng),
326 };
327
328 let builder = AuthTokenMintCallBuilder {
330 coin_attrs: coin_attrs.clone(),
331 token_attrs: token_attrs.clone(),
332 mint_keypair: mint_authority,
333 auth_mint_zkbin,
334 auth_mint_pk,
335 };
336 let auth_debris = builder.build()?;
337 let mut data = vec![MoneyFunction::AuthTokenMintV1 as u8];
338 auth_debris.params.encode_async(&mut data).await?;
339 let auth_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
340
341 let builder = TokenMintCallBuilder { coin_attrs, token_attrs, mint_zkbin, mint_pk };
343 let mint_debris = builder.build()?;
344 let mut data = vec![MoneyFunction::TokenMintV1 as u8];
345 mint_debris.params.encode_async(&mut data).await?;
346 let mint_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
347
348 let mut tx_builder = TransactionBuilder::new(
350 ContractCallLeaf { call: mint_call, proofs: mint_debris.proofs },
351 vec![DarkTree::new(
352 ContractCallLeaf { call: auth_call, proofs: auth_debris.proofs },
353 vec![],
354 None,
355 None,
356 )],
357 )?;
358
359 let mut tx = tx_builder.build()?;
362 let auth_sigs = tx.create_sigs(&[mint_authority.secret])?;
363 let mint_sigs = tx.create_sigs(&[])?;
364 tx.signatures = vec![auth_sigs, mint_sigs];
365
366 let tree = self.get_money_tree().await?;
367 let (fee_call, fee_proofs, fee_secrets) =
368 self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
369
370 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
372
373 let mut tx = tx_builder.build()?;
375 let sigs = tx.create_sigs(&[mint_authority.secret])?;
376 tx.signatures.push(sigs);
377 let sigs = tx.create_sigs(&[])?;
378 tx.signatures.push(sigs);
379 let sigs = tx.create_sigs(&fee_secrets)?;
380 tx.signatures.push(sigs);
381
382 Ok(tx)
383 }
384
385 pub async fn freeze_token(&self, token_id: TokenId) -> Result<Transaction> {
387 let token_mint_authority = self.get_token_mint_authority(&token_id).await?;
389 let token_attrs =
390 self.derive_token_attributes(token_mint_authority.1, token_mint_authority.2);
391 let mint_authority = Keypair::new(token_mint_authority.1);
392
393 assert_eq!(token_id, token_attrs.to_token_id());
395
396 let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
400
401 let Some(auth_mint_zkbin) =
402 zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1)
403 else {
404 return Err(Error::Custom("Auth token mint circuit not found".to_string()))
405 };
406
407 let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
408 else {
409 return Err(Error::Custom("Fee circuit not found".to_string()))
410 };
411
412 let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1)?;
413 let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
414
415 let auth_mint_circuit =
416 ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
417 let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
418
419 let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &auth_mint_circuit);
421 let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
422
423 let builder = AuthTokenFreezeCallBuilder {
425 mint_keypair: mint_authority,
426 token_attrs,
427 auth_mint_zkbin,
428 auth_mint_pk,
429 };
430 let freeze_debris = builder.build()?;
431 let mut data = vec![MoneyFunction::AuthTokenFreezeV1 as u8];
432 freeze_debris.params.encode_async(&mut data).await?;
433 let freeze_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
434
435 let mut tx_builder = TransactionBuilder::new(
437 ContractCallLeaf { call: freeze_call, proofs: freeze_debris.proofs },
438 vec![],
439 )?;
440
441 let mut tx = tx_builder.build()?;
444 let sigs = tx.create_sigs(&[mint_authority.secret])?;
445 tx.signatures.push(sigs);
446
447 let tree = self.get_money_tree().await?;
448 let (fee_call, fee_proofs, fee_secrets) =
449 self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
450
451 tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
453
454 let mut tx = tx_builder.build()?;
456 let sigs = tx.create_sigs(&[mint_authority.secret])?;
457 tx.signatures.push(sigs);
458 let sigs = tx.create_sigs(&fee_secrets)?;
459 tx.signatures.push(sigs);
460
461 Ok(tx)
462 }
463}