drk/
token.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 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_IS_FROZEN, MONEY_TOKENS_COL_MINT_AUTHORITY,
54        MONEY_TOKENS_COL_TOKEN_BLIND, MONEY_TOKENS_COL_TOKEN_ID, MONEY_TOKENS_TABLE,
55    },
56    Drk,
57};
58
59impl Drk {
60    /// Auxiliary function to derive `TokenAttributes` for provided secret key and token blind.
61    fn derive_token_attributes(
62        &self,
63        mint_authority: SecretKey,
64        token_blind: BaseBlind,
65    ) -> TokenAttributes {
66        // Create the Auth FuncID
67        let auth_func_id = FuncRef {
68            contract_id: *MONEY_CONTRACT_ID,
69            func_code: MoneyFunction::AuthTokenMintV1 as u8,
70        }
71        .to_func_id();
72
73        // Grab the mint authority key public coordinates
74        let (mint_auth_x, mint_auth_y) = PublicKey::from_secret(mint_authority).xy();
75
76        // Generate the token attributes
77        TokenAttributes {
78            auth_parent: auth_func_id,
79            user_data: poseidon_hash([mint_auth_x, mint_auth_y]),
80            blind: token_blind,
81        }
82    }
83
84    /// Import a token mint authority into the wallet.
85    pub async fn import_mint_authority(
86        &self,
87        mint_authority: SecretKey,
88        token_blind: BaseBlind,
89    ) -> Result<TokenId> {
90        let token_id = self.derive_token_attributes(mint_authority, token_blind).to_token_id();
91        let is_frozen = 0;
92
93        let query = format!(
94            "INSERT INTO {} ({}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4);",
95            *MONEY_TOKENS_TABLE,
96            MONEY_TOKENS_COL_TOKEN_ID,
97            MONEY_TOKENS_COL_MINT_AUTHORITY,
98            MONEY_TOKENS_COL_TOKEN_BLIND,
99            MONEY_TOKENS_COL_IS_FROZEN,
100        );
101
102        if let Err(e) = self.wallet.exec_sql(
103            &query,
104            rusqlite::params![
105                serialize_async(&token_id).await,
106                serialize_async(&mint_authority).await,
107                serialize_async(&token_blind).await,
108                is_frozen,
109            ],
110        ) {
111            return Err(Error::DatabaseError(format!(
112                "[import_mint_authority] Inserting mint authority failed: {e:?}"
113            )))
114        };
115
116        Ok(token_id)
117    }
118
119    /// Auxiliary function to parse a `MONEY_TOKENS_TABLE` records.
120    /// The boolean in the returned tuples notes if the token mint authority is frozen.
121    async fn parse_mint_authority_record(
122        &self,
123        row: &[Value],
124    ) -> Result<(TokenId, SecretKey, BaseBlind, bool)> {
125        let Value::Blob(ref token_bytes) = row[0] else {
126            return Err(Error::ParseFailed(
127                "[parse_mint_authority_record] Token ID bytes parsing failed",
128            ))
129        };
130        let token_id = deserialize_async(token_bytes).await?;
131
132        let Value::Blob(ref auth_bytes) = row[1] else {
133            return Err(Error::ParseFailed(
134                "[parse_mint_authority_record] Mint authority bytes parsing failed",
135            ))
136        };
137        let mint_authority = deserialize_async(auth_bytes).await?;
138
139        let Value::Blob(ref token_blind_bytes) = row[2] else {
140            return Err(Error::ParseFailed(
141                "[parse_mint_authority_record] Token blind bytes parsing failed",
142            ))
143        };
144        let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
145
146        let Value::Integer(frozen) = row[3] else {
147            return Err(Error::ParseFailed("[parse_mint_authority_record] Is frozen parsing failed"))
148        };
149        let Ok(frozen) = i32::try_from(frozen) else {
150            return Err(Error::ParseFailed("[parse_mint_authority_record] Is frozen parsing failed"))
151        };
152
153        Ok((token_id, mint_authority, token_blind, frozen != 0))
154    }
155
156    /// Reset all token mint authorities frozen status in the wallet.
157    pub fn reset_mint_authorities(&self) -> WalletDbResult<()> {
158        println!("Resetting mint authorities frozen status");
159        let query =
160            format!("UPDATE {} SET {} = 0", *MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN,);
161        self.wallet.exec_sql(&query, &[])?;
162        println!("Successfully mint authorities frozen status");
163
164        Ok(())
165    }
166
167    /// Fetch all token mint authorities from the wallet.
168    pub async fn get_mint_authorities(&self) -> Result<Vec<(TokenId, SecretKey, BaseBlind, bool)>> {
169        let rows = match self.wallet.query_multiple(&MONEY_TOKENS_TABLE, &[], &[]) {
170            Ok(r) => r,
171            Err(e) => {
172                return Err(Error::DatabaseError(format!(
173                    "[get_mint_authorities] Tokens mint autorities retrieval failed: {e:?}"
174                )))
175            }
176        };
177
178        let mut ret = Vec::with_capacity(rows.len());
179        for row in rows {
180            ret.push(self.parse_mint_authority_record(&row).await?);
181        }
182
183        Ok(ret)
184    }
185
186    /// Fetch provided token unfrozen mint authority from the wallet.
187    async fn get_token_mint_authority(
188        &self,
189        token_id: &TokenId,
190    ) -> Result<(TokenId, SecretKey, BaseBlind, bool)> {
191        let row = match self.wallet.query_single(
192            &MONEY_TOKENS_TABLE,
193            &[],
194            convert_named_params! {(MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await)},
195        ) {
196            Ok(r) => r,
197            Err(e) => {
198                return Err(Error::DatabaseError(format!(
199                    "[get_token_mint_authority] Token mint autority retrieval failed: {e:?}"
200                )))
201            }
202        };
203
204        let token = self.parse_mint_authority_record(&row).await?;
205
206        if token.3 {
207            return Err(Error::Custom(
208                "This token mint is marked as frozen in the wallet".to_string(),
209            ))
210        }
211
212        Ok(token)
213    }
214
215    /// Create a token mint transaction. Returns the transaction object on success.
216    pub async fn mint_token(
217        &self,
218        amount: &str,
219        recipient: PublicKey,
220        token_id: TokenId,
221        spend_hook: Option<FuncId>,
222        user_data: Option<pallas::Base>,
223    ) -> Result<Transaction> {
224        // Decode provided amount
225        let amount = decode_base10(amount, BALANCE_BASE10_DECIMALS, false)?;
226
227        // Grab token ID mint authority and attributes
228        let token_mint_authority = self.get_token_mint_authority(&token_id).await?;
229        let token_attrs =
230            self.derive_token_attributes(token_mint_authority.1, token_mint_authority.2);
231        let mint_authority = Keypair::new(token_mint_authority.1);
232
233        // Sanity check
234        assert_eq!(token_id, token_attrs.to_token_id());
235
236        // Now we need to do a lookup for the zkas proof bincodes, and create
237        // the circuit objects and proving keys so we can build the transaction.
238        // We also do this through the RPC.
239        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
240
241        let Some(mint_zkbin) =
242            zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1)
243        else {
244            return Err(Error::Custom("Token mint circuit not found".to_string()))
245        };
246
247        let Some(auth_mint_zkbin) =
248            zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1)
249        else {
250            return Err(Error::Custom("Auth token mint circuit not found".to_string()))
251        };
252
253        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
254        else {
255            return Err(Error::Custom("Fee circuit not found".to_string()))
256        };
257
258        let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
259        let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1)?;
260        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
261
262        let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
263        let auth_mint_circuit =
264            ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
265        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
266
267        // Creating TokenMint, AuthTokenMint and Fee circuits proving keys
268        let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
269        let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &auth_mint_circuit);
270        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
271
272        // Build the coin attributes
273        let coin_attrs = CoinAttributes {
274            public_key: recipient,
275            value: amount,
276            token_id,
277            spend_hook: spend_hook.unwrap_or(FuncId::none()),
278            user_data: user_data.unwrap_or(pallas::Base::ZERO),
279            blind: Blind::random(&mut OsRng),
280        };
281
282        // Create the auth call
283        let builder = AuthTokenMintCallBuilder {
284            coin_attrs: coin_attrs.clone(),
285            token_attrs: token_attrs.clone(),
286            mint_keypair: mint_authority,
287            auth_mint_zkbin,
288            auth_mint_pk,
289        };
290        let auth_debris = builder.build()?;
291        let mut data = vec![MoneyFunction::AuthTokenMintV1 as u8];
292        auth_debris.params.encode_async(&mut data).await?;
293        let auth_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
294
295        // Create the minting call
296        let builder = TokenMintCallBuilder { coin_attrs, token_attrs, mint_zkbin, mint_pk };
297        let mint_debris = builder.build()?;
298        let mut data = vec![MoneyFunction::TokenMintV1 as u8];
299        mint_debris.params.encode_async(&mut data).await?;
300        let mint_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
301
302        // Create the TransactionBuilder containing above calls
303        let mut tx_builder = TransactionBuilder::new(
304            ContractCallLeaf { call: mint_call, proofs: mint_debris.proofs },
305            vec![DarkTree::new(
306                ContractCallLeaf { call: auth_call, proofs: auth_debris.proofs },
307                vec![],
308                None,
309                None,
310            )],
311        )?;
312
313        // We first have to execute the fee-less tx to gather its used gas, and then we feed
314        // it into the fee-creating function.
315        let mut tx = tx_builder.build()?;
316        let auth_sigs = tx.create_sigs(&[mint_authority.secret])?;
317        let mint_sigs = tx.create_sigs(&[])?;
318        tx.signatures = vec![auth_sigs, mint_sigs];
319
320        let tree = self.get_money_tree().await?;
321        let (fee_call, fee_proofs, fee_secrets) =
322            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
323
324        // Append the fee call to the transaction
325        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
326
327        // Now build the actual transaction and sign it with all necessary keys.
328        let mut tx = tx_builder.build()?;
329        let sigs = tx.create_sigs(&[mint_authority.secret])?;
330        tx.signatures.push(sigs);
331        let sigs = tx.create_sigs(&[])?;
332        tx.signatures.push(sigs);
333        let sigs = tx.create_sigs(&fee_secrets)?;
334        tx.signatures.push(sigs);
335
336        Ok(tx)
337    }
338
339    /// Create a token freeze transaction. Returns the transaction object on success.
340    pub async fn freeze_token(&self, token_id: TokenId) -> Result<Transaction> {
341        // Grab token ID mint authority and attributes
342        let token_mint_authority = self.get_token_mint_authority(&token_id).await?;
343        let token_attrs =
344            self.derive_token_attributes(token_mint_authority.1, token_mint_authority.2);
345        let mint_authority = Keypair::new(token_mint_authority.1);
346
347        // Sanity check
348        assert_eq!(token_id, token_attrs.to_token_id());
349
350        // Now we need to do a lookup for the zkas proof bincodes, and create
351        // the circuit objects and proving keys so we can build the transaction.
352        // We also do this through the RPC.
353        let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
354
355        let Some(auth_mint_zkbin) =
356            zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1)
357        else {
358            return Err(Error::Custom("Auth token mint circuit not found".to_string()))
359        };
360
361        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
362        else {
363            return Err(Error::Custom("Fee circuit not found".to_string()))
364        };
365
366        let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1)?;
367        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
368
369        let auth_mint_circuit =
370            ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
371        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
372
373        // Creating AuthTokenMint and Fee circuits proving keys
374        let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &auth_mint_circuit);
375        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
376
377        // Create the freeze call
378        let builder = AuthTokenFreezeCallBuilder {
379            mint_keypair: mint_authority,
380            token_attrs,
381            auth_mint_zkbin,
382            auth_mint_pk,
383        };
384        let freeze_debris = builder.build()?;
385        let mut data = vec![MoneyFunction::AuthTokenFreezeV1 as u8];
386        freeze_debris.params.encode_async(&mut data).await?;
387        let freeze_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
388
389        // Create the TransactionBuilder containing above call
390        let mut tx_builder = TransactionBuilder::new(
391            ContractCallLeaf { call: freeze_call, proofs: freeze_debris.proofs },
392            vec![],
393        )?;
394
395        // We first have to execute the fee-less tx to gather its used gas, and then we feed
396        // it into the fee-creating function.
397        let mut tx = tx_builder.build()?;
398        let sigs = tx.create_sigs(&[mint_authority.secret])?;
399        tx.signatures.push(sigs);
400
401        let tree = self.get_money_tree().await?;
402        let (fee_call, fee_proofs, fee_secrets) =
403            self.append_fee_call(&tx, &tree, &fee_pk, &fee_zkbin, None).await?;
404
405        // Append the fee call to the transaction
406        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
407
408        // Now build the actual transaction and sign it with all necessary keys.
409        let mut tx = tx_builder.build()?;
410        let sigs = tx.create_sigs(&[mint_authority.secret])?;
411        tx.signatures.push(sigs);
412        let sigs = tx.create_sigs(&fee_secrets)?;
413        tx.signatures.push(sigs);
414
415        Ok(tx)
416    }
417}