use std::{collections::HashMap, str::FromStr};
use lazy_static::lazy_static;
use num_bigint::BigUint;
use rand::rngs::OsRng;
use rusqlite::types::Value;
use darkfi::{
tx::Transaction,
zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses, Proof},
zkas::ZkBinary,
Error, Result,
};
use darkfi_money_contract::{
client::{
compute_remainder_blind,
fee_v1::{create_fee_proof, FeeCallInput, FeeCallOutput, FEE_CALL_GAS},
MoneyNote, OwnCoin,
},
model::{
Coin, Input, MoneyAuthTokenFreezeParamsV1, MoneyAuthTokenMintParamsV1, MoneyFeeParamsV1,
MoneyGenesisMintParamsV1, MoneyPoWRewardParamsV1, MoneyTokenMintParamsV1,
MoneyTransferParamsV1, Nullifier, Output, TokenId, DARK_TOKEN_ID,
},
MoneyFunction, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
};
use darkfi_sdk::{
bridgetree,
crypto::{
note::AeadEncryptedNote,
pasta_prelude::PrimeField,
smt::{PoseidonFp, EMPTY_NODES_FP},
BaseBlind, FuncId, Keypair, MerkleNode, MerkleTree, PublicKey, ScalarBlind, SecretKey,
MONEY_CONTRACT_ID,
},
dark_tree::DarkLeaf,
pasta::pallas,
ContractCall,
};
use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
use crate::{
cli_util::kaching,
convert_named_params,
error::WalletDbResult,
walletdb::{WalletSmt, WalletStorage},
Drk,
};
lazy_static! {
pub static ref MONEY_TREE_TABLE: String =
format!("{}_money_tree", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_SMT_TABLE: String = format!("{}_money_smt", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_KEYS_TABLE: String =
format!("{}_money_keys", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_COINS_TABLE: String =
format!("{}_money_coins", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_TOKENS_TABLE: String =
format!("{}_money_tokens", MONEY_CONTRACT_ID.to_string());
pub static ref MONEY_ALIASES_TABLE: String =
format!("{}_money_aliases", MONEY_CONTRACT_ID.to_string());
}
pub const MONEY_TREE_COL_TREE: &str = "tree";
pub const MONEY_SMT_COL_KEY: &str = "smt_key";
pub const MONEY_SMT_COL_VALUE: &str = "smt_value";
pub const MONEY_KEYS_COL_KEY_ID: &str = "key_id";
pub const MONEY_KEYS_COL_IS_DEFAULT: &str = "is_default";
pub const MONEY_KEYS_COL_PUBLIC: &str = "public";
pub const MONEY_KEYS_COL_SECRET: &str = "secret";
pub const MONEY_COINS_COL_COIN: &str = "coin";
pub const MONEY_COINS_COL_IS_SPENT: &str = "is_spent";
pub const MONEY_COINS_COL_VALUE: &str = "value";
pub const MONEY_COINS_COL_TOKEN_ID: &str = "token_id";
pub const MONEY_COINS_COL_SPEND_HOOK: &str = "spend_hook";
pub const MONEY_COINS_COL_USER_DATA: &str = "user_data";
pub const MONEY_COINS_COL_COIN_BLIND: &str = "coin_blind";
pub const MONEY_COINS_COL_VALUE_BLIND: &str = "value_blind";
pub const MONEY_COINS_COL_TOKEN_BLIND: &str = "token_blind";
pub const MONEY_COINS_COL_SECRET: &str = "secret";
pub const MONEY_COINS_COL_LEAF_POSITION: &str = "leaf_position";
pub const MONEY_COINS_COL_MEMO: &str = "memo";
pub const MONEY_COINS_COL_SPENT_TX_HASH: &str = "spent_tx_hash";
pub const MONEY_TOKENS_COL_TOKEN_ID: &str = "token_id";
pub const MONEY_TOKENS_COL_MINT_AUTHORITY: &str = "mint_authority";
pub const MONEY_TOKENS_COL_TOKEN_BLIND: &str = "token_blind";
pub const MONEY_TOKENS_COL_IS_FROZEN: &str = "is_frozen";
pub const MONEY_ALIASES_COL_ALIAS: &str = "alias";
pub const MONEY_ALIASES_COL_TOKEN_ID: &str = "token_id";
pub const BALANCE_BASE10_DECIMALS: usize = 8;
impl Drk {
pub async fn initialize_money(&self) -> WalletDbResult<()> {
let wallet_schema = include_str!("../money.sql");
self.wallet.exec_batch_sql(wallet_schema)?;
if self.get_money_tree().await.is_err() {
println!("Initializing Money Merkle tree");
let mut tree = MerkleTree::new(1);
tree.append(MerkleNode::from(pallas::Base::ZERO));
let _ = tree.mark().unwrap();
let query =
format!("INSERT INTO {} ({}) VALUES (?1);", *MONEY_TREE_TABLE, MONEY_TREE_COL_TREE);
self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&tree).await])?;
println!("Successfully initialized Merkle tree for the Money contract");
}
self.add_alias("DRK".to_string(), *DARK_TOKEN_ID).await?;
Ok(())
}
pub async fn money_keygen(&self) -> WalletDbResult<()> {
println!("Generating a new keypair");
let keypair = Keypair::random(&mut OsRng);
let is_default = 0;
let query = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
*MONEY_KEYS_TABLE,
MONEY_KEYS_COL_IS_DEFAULT,
MONEY_KEYS_COL_PUBLIC,
MONEY_KEYS_COL_SECRET
);
self.wallet.exec_sql(
&query,
rusqlite::params![
is_default,
serialize_async(&keypair.public).await,
serialize_async(&keypair.secret).await
],
)?;
println!("New address:");
println!("{}", keypair.public);
Ok(())
}
pub async fn default_secret(&self) -> Result<SecretKey> {
let row = match self.wallet.query_single(
&MONEY_KEYS_TABLE,
&[MONEY_KEYS_COL_SECRET],
convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[default_secret] Default secret key retrieval failed: {e:?}"
)))
}
};
let Value::Blob(ref key_bytes) = row[0] else {
return Err(Error::ParseFailed("[default_secret] Key bytes parsing failed"))
};
let secret_key: SecretKey = deserialize_async(key_bytes).await?;
Ok(secret_key)
}
pub async fn default_address(&self) -> Result<PublicKey> {
let row = match self.wallet.query_single(
&MONEY_KEYS_TABLE,
&[MONEY_KEYS_COL_PUBLIC],
convert_named_params! {(MONEY_KEYS_COL_IS_DEFAULT, 1)},
) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[default_address] Default address retrieval failed: {e:?}"
)))
}
};
let Value::Blob(ref key_bytes) = row[0] else {
return Err(Error::ParseFailed("[default_address] Key bytes parsing failed"))
};
let public_key: PublicKey = deserialize_async(key_bytes).await?;
Ok(public_key)
}
pub fn set_default_address(&self, idx: usize) -> WalletDbResult<()> {
let is_default = 0;
let query = format!("UPDATE {} SET {} = ?1", *MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT,);
self.wallet.exec_sql(&query, rusqlite::params![is_default])?;
let is_default = 1;
let query = format!(
"UPDATE {} SET {} = ?1 WHERE {} = ?2",
*MONEY_KEYS_TABLE, MONEY_KEYS_COL_IS_DEFAULT, MONEY_KEYS_COL_KEY_ID,
);
self.wallet.exec_sql(&query, rusqlite::params![is_default, idx])
}
pub async fn addresses(&self) -> Result<Vec<(u64, PublicKey, SecretKey, u64)>> {
let rows = match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[], &[]) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[addresses] Addresses retrieval failed: {e:?}"
)))
}
};
let mut vec = Vec::with_capacity(rows.len());
for row in rows {
let Value::Integer(key_id) = row[0] else {
return Err(Error::ParseFailed("[addresses] Key ID parsing failed"))
};
let Ok(key_id) = u64::try_from(key_id) else {
return Err(Error::ParseFailed("[addresses] Key ID parsing failed"))
};
let Value::Integer(is_default) = row[1] else {
return Err(Error::ParseFailed("[addresses] Is default parsing failed"))
};
let Ok(is_default) = u64::try_from(is_default) else {
return Err(Error::ParseFailed("[addresses] Is default parsing failed"))
};
let Value::Blob(ref key_bytes) = row[2] else {
return Err(Error::ParseFailed("[addresses] Public key bytes parsing failed"))
};
let public_key: PublicKey = deserialize_async(key_bytes).await?;
let Value::Blob(ref key_bytes) = row[3] else {
return Err(Error::ParseFailed("[addresses] Secret key bytes parsing failed"))
};
let secret_key: SecretKey = deserialize_async(key_bytes).await?;
vec.push((key_id, public_key, secret_key, is_default));
}
Ok(vec)
}
pub async fn get_money_secrets(&self) -> Result<Vec<SecretKey>> {
let rows =
match self.wallet.query_multiple(&MONEY_KEYS_TABLE, &[MONEY_KEYS_COL_SECRET], &[]) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_money_secrets] Secret keys retrieval failed: {e:?}"
)))
}
};
let mut secrets = Vec::with_capacity(rows.len());
for row in rows {
let Value::Blob(ref key_bytes) = row[0] else {
return Err(Error::ParseFailed(
"[get_money_secrets] Secret key bytes parsing failed",
))
};
let secret_key: SecretKey = deserialize_async(key_bytes).await?;
secrets.push(secret_key);
}
Ok(secrets)
}
pub async fn import_money_secrets(&self, secrets: Vec<SecretKey>) -> Result<Vec<PublicKey>> {
let existing_secrets = self.get_money_secrets().await?;
let mut ret = Vec::with_capacity(secrets.len());
for secret in secrets {
if existing_secrets.contains(&secret) {
println!("Existing key found: {secret}");
continue
}
ret.push(PublicKey::from_secret(secret));
let is_default = 0;
let public = serialize_async(&PublicKey::from_secret(secret)).await;
let secret = serialize_async(&secret).await;
let query = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
*MONEY_KEYS_TABLE,
MONEY_KEYS_COL_IS_DEFAULT,
MONEY_KEYS_COL_PUBLIC,
MONEY_KEYS_COL_SECRET
);
if let Err(e) =
self.wallet.exec_sql(&query, rusqlite::params![is_default, public, secret])
{
return Err(Error::DatabaseError(format!(
"[import_money_secrets] Inserting new address failed: {e:?}"
)))
}
}
Ok(ret)
}
pub async fn money_balance(&self) -> Result<HashMap<String, u64>> {
let mut coins = self.get_coins(false).await?;
coins.retain(|x| x.0.note.spend_hook == FuncId::none());
let mut balmap: HashMap<String, u64> = HashMap::new();
for coin in coins {
let mut value = coin.0.note.value;
if let Some(prev) = balmap.get(&coin.0.note.token_id.to_string()) {
value += prev;
}
balmap.insert(coin.0.note.token_id.to_string(), value);
}
Ok(balmap)
}
pub async fn get_coins(&self, fetch_spent: bool) -> Result<Vec<(OwnCoin, bool, String)>> {
let query = if fetch_spent {
self.wallet.query_multiple(&MONEY_COINS_TABLE, &[], &[])
} else {
self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false)},
)
};
let rows = match query {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_coins] Coins retrieval failed: {e:?}"
)))
}
};
let mut owncoins = Vec::with_capacity(rows.len());
for row in rows {
owncoins.push(self.parse_coin_record(&row).await?)
}
Ok(owncoins)
}
pub async fn get_transaction_coins(&self, spent_tx_hash: &String) -> Result<Vec<OwnCoin>> {
let query = self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_SPENT_TX_HASH, spent_tx_hash)},
);
let rows = match query {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_transaction_coins] Coins retrieval failed: {e:?}"
)))
}
};
let mut owncoins = Vec::with_capacity(rows.len());
for row in rows {
owncoins.push(self.parse_coin_record(&row).await?.0)
}
Ok(owncoins)
}
pub async fn get_token_coins(&self, token_id: &TokenId) -> Result<Vec<OwnCoin>> {
let query = self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)},
);
let rows = match query {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_token_coins] Coins retrieval failed: {e:?}"
)))
}
};
let mut owncoins = Vec::with_capacity(rows.len());
for row in rows {
owncoins.push(self.parse_coin_record(&row).await?.0)
}
Ok(owncoins)
}
pub async fn get_contract_token_coins(
&self,
token_id: &TokenId,
spend_hook: &FuncId,
user_data: &pallas::Base,
) -> Result<Vec<OwnCoin>> {
let query = self.wallet.query_multiple(
&MONEY_COINS_TABLE,
&[],
convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(spend_hook).await), (MONEY_COINS_COL_USER_DATA, serialize_async(user_data).await)},
);
let rows = match query {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_contract_token_coins] Coins retrieval failed: {e:?}"
)))
}
};
let mut owncoins = Vec::with_capacity(rows.len());
for row in rows {
owncoins.push(self.parse_coin_record(&row).await?.0)
}
Ok(owncoins)
}
async fn parse_coin_record(&self, row: &[Value]) -> Result<(OwnCoin, bool, String)> {
let Value::Blob(ref coin_bytes) = row[0] else {
return Err(Error::ParseFailed("[parse_coin_record] Coin bytes parsing failed"))
};
let coin: Coin = deserialize_async(coin_bytes).await?;
let Value::Integer(is_spent) = row[1] else {
return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
};
let Ok(is_spent) = u64::try_from(is_spent) else {
return Err(Error::ParseFailed("[parse_coin_record] Is spent parsing failed"))
};
let is_spent = is_spent > 0;
let Value::Blob(ref value_bytes) = row[2] else {
return Err(Error::ParseFailed("[parse_coin_record] Value bytes parsing failed"))
};
let value: u64 = deserialize_async(value_bytes).await?;
let Value::Blob(ref token_id_bytes) = row[3] else {
return Err(Error::ParseFailed("[parse_coin_record] Token ID bytes parsing failed"))
};
let token_id: TokenId = deserialize_async(token_id_bytes).await?;
let Value::Blob(ref spend_hook_bytes) = row[4] else {
return Err(Error::ParseFailed("[parse_coin_record] Spend hook bytes parsing failed"))
};
let spend_hook: pallas::Base = deserialize_async(spend_hook_bytes).await?;
let Value::Blob(ref user_data_bytes) = row[5] else {
return Err(Error::ParseFailed("[parse_coin_record] User data bytes parsing failed"))
};
let user_data: pallas::Base = deserialize_async(user_data_bytes).await?;
let Value::Blob(ref coin_blind_bytes) = row[6] else {
return Err(Error::ParseFailed("[parse_coin_record] Coin blind bytes parsing failed"))
};
let coin_blind: BaseBlind = deserialize_async(coin_blind_bytes).await?;
let Value::Blob(ref value_blind_bytes) = row[7] else {
return Err(Error::ParseFailed("[parse_coin_record] Value blind bytes parsing failed"))
};
let value_blind: ScalarBlind = deserialize_async(value_blind_bytes).await?;
let Value::Blob(ref token_blind_bytes) = row[8] else {
return Err(Error::ParseFailed("[parse_coin_record] Token blind bytes parsing failed"))
};
let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
let Value::Blob(ref secret_bytes) = row[9] else {
return Err(Error::ParseFailed("[parse_coin_record] Secret bytes parsing failed"))
};
let secret: SecretKey = deserialize_async(secret_bytes).await?;
let Value::Blob(ref leaf_position_bytes) = row[10] else {
return Err(Error::ParseFailed("[parse_coin_record] Leaf position bytes parsing failed"))
};
let leaf_position: bridgetree::Position = deserialize_async(leaf_position_bytes).await?;
let Value::Blob(ref memo) = row[11] else {
return Err(Error::ParseFailed("[parse_coin_record] Memo parsing failed"))
};
let Value::Text(ref spent_tx_hash) = row[12] else {
return Err(Error::ParseFailed(
"[parse_coin_record] Spent transaction hash parsing failed",
))
};
let note = MoneyNote {
value,
token_id,
spend_hook: spend_hook.into(),
user_data,
coin_blind,
value_blind,
token_blind,
memo: memo.clone(),
};
Ok((OwnCoin { coin, note, secret, leaf_position }, is_spent, spent_tx_hash.clone()))
}
pub async fn add_alias(&self, alias: String, token_id: TokenId) -> WalletDbResult<()> {
println!("Generating alias {alias} for Token: {token_id}");
let query = format!(
"INSERT OR REPLACE INTO {} ({}, {}) VALUES (?1, ?2);",
*MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS, MONEY_ALIASES_COL_TOKEN_ID,
);
self.wallet.exec_sql(
&query,
rusqlite::params![serialize_async(&alias).await, serialize_async(&token_id).await],
)
}
pub async fn get_aliases(
&self,
alias_filter: Option<String>,
token_id_filter: Option<TokenId>,
) -> Result<HashMap<String, TokenId>> {
let rows = match self.wallet.query_multiple(&MONEY_ALIASES_TABLE, &[], &[]) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_aliases] Aliases retrieval failed: {e:?}"
)))
}
};
let mut map: HashMap<String, TokenId> = HashMap::new();
for row in rows {
let Value::Blob(ref alias_bytes) = row[0] else {
return Err(Error::ParseFailed("[get_aliases] Alias bytes parsing failed"))
};
let alias: String = deserialize_async(alias_bytes).await?;
if alias_filter.is_some() && alias_filter.as_ref().unwrap() != &alias {
continue
}
let Value::Blob(ref token_id_bytes) = row[1] else {
return Err(Error::ParseFailed("[get_aliases] TokenId bytes parsing failed"))
};
let token_id: TokenId = deserialize_async(token_id_bytes).await?;
if token_id_filter.is_some() && token_id_filter.as_ref().unwrap() != &token_id {
continue
}
map.insert(alias, token_id);
}
Ok(map)
}
pub async fn get_aliases_mapped_by_token(&self) -> Result<HashMap<String, String>> {
let aliases = self.get_aliases(None, None).await?;
let mut map: HashMap<String, String> = HashMap::new();
for (alias, token_id) in aliases {
let aliases_string = if let Some(prev) = map.get(&token_id.to_string()) {
format!("{}, {}", prev, alias)
} else {
alias
};
map.insert(token_id.to_string(), aliases_string);
}
Ok(map)
}
pub async fn remove_alias(&self, alias: String) -> WalletDbResult<()> {
println!("Removing alias: {alias}");
let query = format!(
"DELETE FROM {} WHERE {} = ?1;",
*MONEY_ALIASES_TABLE, MONEY_ALIASES_COL_ALIAS,
);
self.wallet.exec_sql(&query, rusqlite::params![serialize_async(&alias).await])
}
pub async fn unspend_coin(&self, coin: &Coin) -> WalletDbResult<()> {
let is_spend = 0;
let query = format!(
"UPDATE {} SET {} = ?1, {} = ?2 WHERE {} = ?3;",
*MONEY_COINS_TABLE,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_SPENT_TX_HASH,
MONEY_COINS_COL_COIN
);
self.wallet.exec_sql(
&query,
rusqlite::params![is_spend, "-", serialize_async(&coin.inner()).await],
)
}
pub async fn put_money_tree(&self, tree: &MerkleTree) -> WalletDbResult<()> {
let query = format!("UPDATE {} SET {} = ?1;", *MONEY_TREE_TABLE, MONEY_TREE_COL_TREE);
self.wallet.exec_sql(&query, rusqlite::params![serialize_async(tree).await])
}
pub async fn get_money_tree(&self) -> Result<MerkleTree> {
let row = match self.wallet.query_single(&MONEY_TREE_TABLE, &[MONEY_TREE_COL_TREE], &[]) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_money_tree] Tree retrieval failed: {e:?}"
)))
}
};
let Value::Blob(ref tree_bytes) = row[0] else {
return Err(Error::ParseFailed("[get_money_tree] Tree bytes parsing failed"))
};
let tree = deserialize_async(tree_bytes).await?;
Ok(tree)
}
pub async fn get_money_tree_state_query(&self) -> Result<String> {
let tree = self.get_money_tree().await?;
match self.wallet.create_prepared_statement(
&format!("UPDATE {} SET {} = ?1;", *MONEY_TREE_TABLE, MONEY_TREE_COL_TREE),
rusqlite::params![serialize_async(&tree).await],
) {
Ok(q) => Ok(q),
Err(e) => Err(Error::DatabaseError(format!(
"[get_money_tree_state_query] Creating query for money tree failed: {e:?}"
))),
}
}
pub async fn get_nullifiers_smt(&self) -> Result<HashMap<BigUint, pallas::Base>> {
let rows = match self.wallet.query_multiple(&MONEY_SMT_TABLE, &[], &[]) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[get_nullifiers_smt] SMT records retrieval failed: {e:?}"
)))
}
};
let mut smt = HashMap::new();
for row in rows {
let Value::Blob(ref key_bytes) = row[0] else {
return Err(Error::ParseFailed("[get_nullifiers_smt] Key bytes parsing failed"))
};
let key = BigUint::from_bytes_le(key_bytes);
let Value::Blob(ref value_bytes) = row[1] else {
return Err(Error::ParseFailed("[get_nullifiers_smt] Value bytes parsing failed"))
};
let mut repr = [0; 32];
repr.copy_from_slice(value_bytes);
let Some(value) = pallas::Base::from_repr(repr).into() else {
return Err(Error::ParseFailed("[get_nullifiers_smt] Value conversion failed"))
};
smt.insert(key, value);
}
Ok(smt)
}
async fn parse_money_call(
&self,
call_idx: usize,
calls: &[DarkLeaf<ContractCall>],
) -> Result<(Vec<Nullifier>, Vec<Coin>, Vec<AeadEncryptedNote>, Vec<TokenId>)> {
let mut nullifiers: Vec<Nullifier> = vec![];
let mut coins: Vec<Coin> = vec![];
let mut notes: Vec<AeadEncryptedNote> = vec![];
let mut freezes: Vec<TokenId> = vec![];
let call = &calls[call_idx];
let data = &call.data.data;
match MoneyFunction::try_from(data[0])? {
MoneyFunction::FeeV1 => {
println!("[parse_money_call] Found Money::FeeV1 call");
let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
nullifiers.push(params.input.nullifier);
coins.push(params.output.coin);
notes.push(params.output.note);
}
MoneyFunction::GenesisMintV1 => {
println!("[parse_money_call] Found Money::GenesisMintV1 call");
let params: MoneyGenesisMintParamsV1 = deserialize_async(&data[1..]).await?;
coins.push(params.output.coin);
notes.push(params.output.note);
}
MoneyFunction::PoWRewardV1 => {
println!("[parse_money_call] Found Money::PoWRewardV1 call");
let params: MoneyPoWRewardParamsV1 = deserialize_async(&data[1..]).await?;
coins.push(params.output.coin);
notes.push(params.output.note);
}
MoneyFunction::TransferV1 => {
println!("[parse_money_call] Found Money::TransferV1 call");
let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
for input in params.inputs {
nullifiers.push(input.nullifier);
}
for output in params.outputs {
coins.push(output.coin);
notes.push(output.note);
}
}
MoneyFunction::OtcSwapV1 => {
println!("[parse_money_call] Found Money::OtcSwapV1 call");
let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
for input in params.inputs {
nullifiers.push(input.nullifier);
}
for output in params.outputs {
coins.push(output.coin);
notes.push(output.note);
}
}
MoneyFunction::AuthTokenMintV1 => {
println!("[parse_money_call] Found Money::AuthTokenMintV1 call");
}
MoneyFunction::AuthTokenFreezeV1 => {
println!("[parse_money_call] Found Money::AuthTokenFreezeV1 call");
let params: MoneyAuthTokenFreezeParamsV1 = deserialize_async(&data[1..]).await?;
freezes.push(params.token_id);
}
MoneyFunction::TokenMintV1 => {
println!("[parse_money_call] Found Money::TokenMintV1 call");
let params: MoneyTokenMintParamsV1 = deserialize_async(&data[1..]).await?;
coins.push(params.coin);
let child_idx = call.children_indexes[0];
let child_call = &calls[child_idx];
let params: MoneyAuthTokenMintParamsV1 =
deserialize_async(&child_call.data.data[1..]).await?;
notes.push(params.enc_note);
}
}
Ok((nullifiers, coins, notes, freezes))
}
pub async fn apply_tx_money_data(
&self,
call_idx: usize,
calls: &[DarkLeaf<ContractCall>],
tx_hash: &String,
) -> Result<bool> {
let (nullifiers, coins, notes, freezes) = self.parse_money_call(call_idx, calls).await?;
let secrets = self.get_money_secrets().await?;
let dao_secrets = self.get_dao_secrets().await?;
let mut tree = self.get_money_tree().await?;
let mut owncoins = vec![];
for (coin, note) in coins.iter().zip(notes.iter()) {
tree.append(MerkleNode::from(coin.inner()));
for secret in secrets.iter().chain(dao_secrets.iter()) {
if let Ok(note) = note.decrypt::<MoneyNote>(secret) {
println!("[apply_tx_money_data] Successfully decrypted a Money Note");
println!("[apply_tx_money_data] Witnessing coin in Merkle tree");
let leaf_position = tree.mark().unwrap();
let owncoin =
OwnCoin { coin: *coin, note: note.clone(), secret: *secret, leaf_position };
owncoins.push(owncoin);
}
}
}
if let Err(e) = self.put_money_tree(&tree).await {
return Err(Error::DatabaseError(format!(
"[apply_tx_money_data] Put Money tree failed: {e:?}"
)))
}
self.smt_insert(&nullifiers)?;
let wallet_spent_coins = self.mark_spent_coins(&nullifiers, tx_hash).await?;
let query = format!(
"INSERT INTO {} ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12);",
*MONEY_COINS_TABLE,
MONEY_COINS_COL_COIN,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_VALUE,
MONEY_COINS_COL_TOKEN_ID,
MONEY_COINS_COL_SPEND_HOOK,
MONEY_COINS_COL_USER_DATA,
MONEY_COINS_COL_COIN_BLIND,
MONEY_COINS_COL_VALUE_BLIND,
MONEY_COINS_COL_TOKEN_BLIND,
MONEY_COINS_COL_SECRET,
MONEY_COINS_COL_LEAF_POSITION,
MONEY_COINS_COL_MEMO,
);
let inverse_query =
format!("DELETE FROM {} WHERE {} = ?1;", *MONEY_COINS_TABLE, MONEY_COINS_COL_COIN);
println!("Found {} OwnCoin(s) in transaction", owncoins.len());
for owncoin in &owncoins {
println!("OwnCoin: {:?}", owncoin.coin);
let key = serialize_async(&owncoin.coin).await;
let inverse =
match self.wallet.create_prepared_statement(&inverse_query, rusqlite::params![key])
{
Ok(q) => q,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[apply_tx_money_data] Creating Money coin insert inverse query failed: {e:?}"
)))
}
};
let params = rusqlite::params![
key,
0, serialize_async(&owncoin.note.value).await,
serialize_async(&owncoin.note.token_id).await,
serialize_async(&owncoin.note.spend_hook).await,
serialize_async(&owncoin.note.user_data).await,
serialize_async(&owncoin.note.coin_blind).await,
serialize_async(&owncoin.note.value_blind).await,
serialize_async(&owncoin.note.token_blind).await,
serialize_async(&owncoin.secret).await,
serialize_async(&owncoin.leaf_position).await,
serialize_async(&owncoin.note.memo).await,
];
if let Err(e) = self.wallet.exec_sql(&query, params) {
return Err(Error::DatabaseError(format!(
"[apply_tx_money_data] Inserting Money coin failed: {e:?}"
)))
}
if let Err(e) = self.wallet.cache_inverse(inverse) {
return Err(Error::DatabaseError(format!(
"[apply_tx_money_data] Inserting inverse query into cache failed: {e:?}"
)))
}
}
let query = format!(
"UPDATE {} SET {} = 1 WHERE {} = ?1;",
*MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID,
);
let inverse_query = format!(
"UPDATE {} SET {} = 0 WHERE {} = ?1;",
*MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_TOKEN_ID,
);
for token_id in &freezes {
let key = serialize_async(token_id).await;
let inverse =
match self.wallet.create_prepared_statement(&inverse_query, rusqlite::params![key])
{
Ok(q) => q,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[apply_tx_money_data] Creating Money token freeze inverse query failed: {e:?}"
)))
}
};
if let Err(e) = self.wallet.exec_sql(&query, rusqlite::params![key]) {
return Err(Error::DatabaseError(format!(
"[apply_tx_money_data] Update Money token freeze failed: {e:?}"
)))
}
if let Err(e) = self.wallet.cache_inverse(inverse) {
return Err(Error::DatabaseError(format!(
"[apply_tx_money_data] Inserting inverse query into cache failed: {e:?}"
)))
}
}
if self.fun && !owncoins.is_empty() {
kaching().await;
}
Ok(wallet_spent_coins || !owncoins.is_empty() || !freezes.is_empty())
}
async fn money_call_nullifiers(&self, call: &DarkLeaf<ContractCall>) -> Result<Vec<Nullifier>> {
let mut nullifiers: Vec<Nullifier> = vec![];
let data = &call.data.data;
match MoneyFunction::try_from(data[0])? {
MoneyFunction::FeeV1 => {
let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
nullifiers.push(params.input.nullifier);
}
MoneyFunction::TransferV1 => {
let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
for input in params.inputs {
nullifiers.push(input.nullifier);
}
}
MoneyFunction::OtcSwapV1 => {
let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
for input in params.inputs {
nullifiers.push(input.nullifier);
}
}
_ => { }
}
Ok(nullifiers)
}
pub async fn mark_tx_spend(&self, tx: &Transaction) -> Result<()> {
let tx_hash = tx.hash().to_string();
println!("[mark_tx_spend] Processing transaction: {tx_hash}");
for (i, call) in tx.calls.iter().enumerate() {
if call.data.contract_id != *MONEY_CONTRACT_ID {
continue
}
println!("[mark_tx_spend] Found Money contract in call {i}");
let nullifiers = self.money_call_nullifiers(call).await?;
self.mark_spent_coins(&nullifiers, &tx_hash).await?;
}
Ok(())
}
pub async fn mark_spent_coin(&self, coin: &Coin, spent_tx_hash: &String) -> WalletDbResult<()> {
let key = serialize_async(&coin.inner()).await;
let query = format!(
"UPDATE {} SET {} = 1, {} = ?1 WHERE {} = ?2;",
*MONEY_COINS_TABLE,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_SPENT_TX_HASH,
MONEY_COINS_COL_COIN
);
let inverse = self.wallet.create_prepared_statement(
&format!(
"UPDATE {} SET {} = 0, {} = '-' WHERE {} = ?1;",
*MONEY_COINS_TABLE,
MONEY_COINS_COL_IS_SPENT,
MONEY_COINS_COL_SPENT_TX_HASH,
MONEY_COINS_COL_COIN
),
rusqlite::params![key],
)?;
self.wallet.exec_sql(&query, rusqlite::params![spent_tx_hash, key])?;
self.wallet.cache_inverse(inverse)
}
pub async fn mark_spent_coins(
&self,
nullifiers: &[Nullifier],
spent_tx_hash: &String,
) -> Result<bool> {
if nullifiers.is_empty() {
return Ok(false)
}
let mut wallet_spent_coins = false;
for coin in self.get_transaction_coins(spent_tx_hash).await? {
if let Err(e) = self.mark_spent_coin(&coin.coin, spent_tx_hash).await {
return Err(Error::DatabaseError(format!(
"[mark_spent_coins] Marking spent coin failed: {e:?}"
)))
}
wallet_spent_coins = true;
}
for (coin, _, _) in self.get_coins(false).await? {
if !nullifiers.contains(&coin.nullifier()) {
continue
}
if let Err(e) = self.mark_spent_coin(&coin.coin, spent_tx_hash).await {
return Err(Error::DatabaseError(format!(
"[mark_spent_coins] Marking spent coin failed: {e:?}"
)))
}
wallet_spent_coins = true;
}
Ok(wallet_spent_coins)
}
pub fn smt_insert(&self, nullifiers: &[Nullifier]) -> Result<()> {
let store = WalletStorage::new(
&self.wallet,
&MONEY_SMT_TABLE,
MONEY_SMT_COL_KEY,
MONEY_SMT_COL_VALUE,
);
let mut smt = WalletSmt::new(store, PoseidonFp::new(), &EMPTY_NODES_FP);
let leaves: Vec<_> = nullifiers.iter().map(|x| (x.inner(), x.inner())).collect();
smt.insert_batch(leaves)?;
Ok(())
}
pub async fn reset_money_tree(&self) -> WalletDbResult<()> {
println!("Resetting Money Merkle tree");
let mut tree = MerkleTree::new(1);
tree.append(MerkleNode::from(pallas::Base::ZERO));
let _ = tree.mark().unwrap();
self.put_money_tree(&tree).await?;
println!("Successfully reset Money Merkle tree");
Ok(())
}
pub fn reset_money_smt(&self) -> WalletDbResult<()> {
println!("Resetting Money Sparse Merkle tree");
let query = format!("DELETE FROM {};", *MONEY_SMT_TABLE);
self.wallet.exec_sql(&query, &[])?;
println!("Successfully reset Money Sparse Merkle tree");
Ok(())
}
pub fn reset_money_coins(&self) -> WalletDbResult<()> {
println!("Resetting coins");
let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE);
self.wallet.exec_sql(&query, &[])?;
println!("Successfully reset coins");
Ok(())
}
pub async fn get_token(&self, input: String) -> Result<TokenId> {
if input.chars().count() <= 5 {
let aliases = self.get_aliases(Some(input.clone()), None).await?;
if let Some(token_id) = aliases.get(&input) {
return Ok(*token_id)
}
}
Ok(TokenId::from_str(input.as_str())?)
}
pub async fn append_fee_call(
&self,
tx: &Transaction,
money_merkle_tree: &MerkleTree,
fee_pk: &ProvingKey,
fee_zkbin: &ZkBinary,
spent_coins: Option<&[OwnCoin]>,
) -> Result<(ContractCall, Vec<Proof>, Vec<SecretKey>)> {
let gas_used = FEE_CALL_GAS + self.get_tx_gas(tx, false).await?;
let mut available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
available_coins.retain(|x| x.note.value > gas_used);
if let Some(spent_coins) = spent_coins {
available_coins.retain(|x| !spent_coins.contains(x));
}
if available_coins.is_empty() {
return Err(Error::Custom("Not enough native tokens to pay for fees".to_string()))
}
let coin = &available_coins[0];
let change_value = coin.note.value - gas_used;
let input = FeeCallInput {
coin: coin.clone(),
merkle_path: money_merkle_tree.witness(coin.leaf_position, 0).unwrap(),
user_data_blind: BaseBlind::random(&mut OsRng),
};
let output = FeeCallOutput {
public_key: PublicKey::from_secret(coin.secret),
value: change_value,
token_id: coin.note.token_id,
blind: BaseBlind::random(&mut OsRng),
spend_hook: FuncId::none(),
user_data: pallas::Base::ZERO,
};
let token_blind = BaseBlind::random(&mut OsRng);
let input_value_blind = ScalarBlind::random(&mut OsRng);
let fee_value_blind = ScalarBlind::random(&mut OsRng);
let output_value_blind = compute_remainder_blind(&[input_value_blind], &[fee_value_blind]);
let signature_secret = SecretKey::random(&mut OsRng);
let (proof, public_inputs) = create_fee_proof(
fee_zkbin,
fee_pk,
&input,
input_value_blind,
&output,
output_value_blind,
output.spend_hook,
output.user_data,
output.blind,
token_blind,
signature_secret,
)?;
let note = MoneyNote {
coin_blind: output.blind,
value: output.value,
token_id: output.token_id,
spend_hook: output.spend_hook,
user_data: output.user_data,
value_blind: output_value_blind,
token_blind,
memo: vec![],
};
let encrypted_note = AeadEncryptedNote::encrypt(¬e, &output.public_key, &mut OsRng)?;
let params = MoneyFeeParamsV1 {
input: Input {
value_commit: public_inputs.input_value_commit,
token_commit: public_inputs.token_commit,
nullifier: public_inputs.nullifier,
merkle_root: public_inputs.merkle_root,
user_data_enc: public_inputs.input_user_data_enc,
signature_public: public_inputs.signature_public,
},
output: Output {
value_commit: public_inputs.output_value_commit,
token_commit: public_inputs.token_commit,
coin: public_inputs.output_coin,
note: encrypted_note,
},
fee_value_blind,
token_blind,
};
let mut data = vec![MoneyFunction::FeeV1 as u8];
gas_used.encode_async(&mut data).await?;
params.encode_async(&mut data).await?;
let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
Ok((call, vec![proof], vec![signature_secret]))
}
pub async fn attach_fee(&self, tx: &mut Transaction) -> Result<()> {
let mut tx_nullifiers = vec![];
for call in &tx.calls {
if call.data.contract_id != *MONEY_CONTRACT_ID {
continue
}
match MoneyFunction::try_from(call.data.data[0])? {
MoneyFunction::FeeV1 => {
return Err(Error::Custom("Fee call already exists".to_string()))
}
_ => { }
}
let nullifiers = self.money_call_nullifiers(call).await?;
tx_nullifiers.extend_from_slice(&nullifiers);
}
let mut spent_coins = vec![];
let available_coins = self.get_token_coins(&DARK_TOKEN_ID).await?;
for coin in available_coins {
if tx_nullifiers.contains(&coin.nullifier()) {
spent_coins.push(coin);
}
}
let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
else {
return Err(Error::Custom("Fee circuit not found".to_string()))
};
let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
let tree = self.get_money_tree().await?;
let (fee_call, fee_proofs, fee_secrets) =
self.append_fee_call(tx, &tree, &fee_pk, &fee_zkbin, Some(&spent_coins)).await?;
tx.calls.push(DarkLeaf { data: fee_call, parent_index: None, children_indexes: vec![] });
tx.proofs.push(fee_proofs);
let sigs = tx.create_sigs(&fee_secrets)?;
tx.signatures.push(sigs);
Ok(())
}
}