diff --git a/clients/nodejs/transfer_receive.js b/clients/nodejs/transfer_receive.js index ec41fea0..19b3ea30 100644 --- a/clients/nodejs/transfer_receive.js +++ b/clients/nodejs/transfer_receive.js @@ -27,36 +27,80 @@ const execute = async (electrumClient, db, wallet_name) => { const serverInfo = await utils.infoConfig(electrumClient); - let received_statechain_ids = []; + let uniqueAuthPubkeys = new Set(); - for (let coin of wallet.coins) { + wallet.coins.forEach(coin => { + uniqueAuthPubkeys.add(coin.auth_pubkey); + }); - if (coin.status != CoinStatus.INITIALISED) { - continue; + let encMsgsPerAuthPubkey = new Map(); + + for (let authPubkey of uniqueAuthPubkeys) { + try { + let encMessages = await getMsgAddr(authPubkey); + if (encMessages.length === 0) { + console.log("No messages"); + continue; + } + + encMsgsPerAuthPubkey.set(authPubkey, encMessages); + } catch (err) { + console.error(err); } + } - // console.log("----\nuser_pubkey", coin.user_pubkey); - // console.log("auth_pubkey", coin.auth_pubkey); - // console.log("statechain_id", coin.statechain_id); - // console.log("coin.amount", coin.amount); - // console.log("coin.status", coin.status); + let receivedStatechainIds = []; - let encMessages = await get_msg_addr(coin.auth_pubkey); + let tempCoins = [...wallet.coins]; + let tempActivities = [...wallet.activities]; - if (encMessages.length == 0) { - continue; - } + for (let [authPubkey, encMessages] of encMsgsPerAuthPubkey.entries()) { + + for (let encMessage of encMessages) { - const statechain_ids_added = await process_encrypted_message(electrumClient, db, coin, encMessages, wallet.network, serverInfo, wallet.activities); - received_statechain_ids = [...received_statechain_ids, ...statechain_ids_added]; + let coin = tempCoins.find(coin => coin.auth_pubkey === authPubkey && coin.status === 'INITIALISED'); + + if (coin) { + try { + let statechainIdAdded = await processEncryptedMessage(electrumClient, db, coin, encMessage, wallet.network, serverInfo, tempActivities); + + if (statechainIdAdded) { + receivedStatechainIds.push(statechainIdAdded); + } + } catch (error) { + console.error(`Error: ${error.message}`); + continue; + } + + } else { + try { + let newCoin = await mercury_wasm.duplicateCoinToInitializedState(wallet, authPubkey); + + if (newCoin) { + let statechainIdAdded = await processEncryptedMessage(electrumClient, db, newCoin, encMessage, wallet.network, serverInfo, tempActivities); + + if (statechainIdAdded) { + tempCoins.push(newCoin); + receivedStatechainIds.push(statechainIdAdded); + } + } + } catch (error) { + console.error(`Error: ${error.message}`); + continue; + } + } + } } + wallet.coins = [...tempCoins]; + wallet.activities = [...tempActivities]; + await sqlite_manager.updateWallet(db, wallet); - return received_statechain_ids; + return receivedStatechainIds; } -const get_msg_addr = async (auth_pubkey) => { +const getMsgAddr = async (auth_pubkey) => { const statechain_entity_url = config.get('statechainEntity'); const path = "transfer/get_msg_addr/"; @@ -75,149 +119,134 @@ const get_msg_addr = async (auth_pubkey) => { return response.data.list_enc_transfer_msg; } -const process_encrypted_message = async (electrumClient, db, coin, encMessages, network, serverInfo, activities) => { +const processEncryptedMessage = async (electrumClient, db, coin, encMessage, network, serverInfo, activities) => { let clientAuthKey = coin.auth_privkey; let newUserPubkey = coin.user_pubkey; - let statechain_ids_added = []; - - for (let encMessage of encMessages) { + let transferMsg = mercury_wasm.decryptTransferMsg(encMessage, clientAuthKey); - let transferMsg = mercury_wasm.decryptTransferMsg(encMessage, clientAuthKey); + let tx0Outpoint = mercury_wasm.getTx0Outpoint(transferMsg.backup_transactions); - let tx0Outpoint = mercury_wasm.getTx0Outpoint(transferMsg.backup_transactions); + const tx0Hex = await getTx0(electrumClient, tx0Outpoint.txid); - const tx0Hex = await getTx0(electrumClient, tx0Outpoint.txid); + const isTransferSignatureValid = mercury_wasm.verifyTransferSignature(newUserPubkey, tx0Outpoint, transferMsg); - const isTransferSignatureValid = mercury_wasm.verifyTransferSignature(newUserPubkey, tx0Outpoint, transferMsg); + if (!isTransferSignatureValid) { + throw new Error("Invalid transfer signature"); + } + + const statechainInfo = await utils.getStatechainInfo(transferMsg.statechain_id); - if (!isTransferSignatureValid) { - console.error("Invalid transfer signature"); - continue; - } - - const statechainInfo = await utils.getStatechainInfo(transferMsg.statechain_id); + if (statechainInfo == null) { + throw new Error("Statechain info not found"); + } - if (statechainInfo == null) { - console.error("Statechain info not found"); - continue; - } + const isTx0OutputPubkeyValid = mercury_wasm.validateTx0OutputPubkey(statechainInfo.enclave_public_key, transferMsg, tx0Outpoint, tx0Hex, network); - const isTx0OutputPubkeyValid = mercury_wasm.validateTx0OutputPubkey(statechainInfo.enclave_public_key, transferMsg, tx0Outpoint, tx0Hex, network); + if (!isTx0OutputPubkeyValid) { + throw new Error("Invalid tx0 output pubkey"); + } - if (!isTx0OutputPubkeyValid) { - console.error("Invalid tx0 output pubkey"); - continue; - } + let latestBackupTxPaysToUserPubkey = mercury_wasm.verifyLatestBackupTxPaysToUserPubkey(transferMsg, newUserPubkey, network); - let latestBackupTxPaysToUserPubkey = mercury_wasm.verifyLatestBackupTxPaysToUserPubkey(transferMsg, newUserPubkey, network); + if (!latestBackupTxPaysToUserPubkey) { + throw new Error("Latest Backup Tx does not pay to the expected public key"); + } - if (!latestBackupTxPaysToUserPubkey) { - console.error("Latest Backup Tx does not pay to the expected public key"); - continue; - } + if (statechainInfo.num_sigs != transferMsg.backup_transactions.length) { + throw new Error("num_sigs is not correct"); + } + + let isTx0OutputUnspent = await verifyTx0OutputIsUnspentAndConfirmed(electrumClient, tx0Outpoint, tx0Hex, network); + if (!isTx0OutputUnspent.result) { + throw new Error("tx0 output is spent or not confirmed"); + } - if (statechainInfo.num_sigs != transferMsg.backup_transactions.length) { - console.error("num_sigs is not correct"); - continue; - } - - let isTx0OutputUnspent = await verifyTx0OutputIsUnspentAndConfirmed(electrumClient, tx0Outpoint, tx0Hex, network); - if (!isTx0OutputUnspent.result) { - console.error("tx0 output is spent or not confirmed"); - continue; - } + const currentFeeRateSatsPerByte = serverInfo.fee_rate_sats_per_byte; - const currentFeeRateSatsPerByte = serverInfo.fee_rate_sats_per_byte; + const feeRateTolerance = config.get('feeRateTolerance'); - const feeRateTolerance = config.get('feeRateTolerance'); + let previousLockTime = null; - let previousLockTime = null; + let sigSchemeValidation = true; - let sigSchemeValidation = true; + for (const [index, backupTx] of transferMsg.backup_transactions.entries()) { - for (const [index, backupTx] of transferMsg.backup_transactions.entries()) { + const isSignatureValid = mercury_wasm.verifyTransactionSignature(backupTx.tx, tx0Hex, feeRateTolerance, currentFeeRateSatsPerByte); - const isSignatureValid = mercury_wasm.verifyTransactionSignature(backupTx.tx, tx0Hex, feeRateTolerance, currentFeeRateSatsPerByte); + if (!isSignatureValid.result) { + console.error(`Invalid signature, ${isSignatureValid.result.msg}`); + sigSchemeValidation = false; + break; + } - if (!isSignatureValid.result) { - console.error(`Invalid signature, ${isSignatureValid.result.msg}`); - sigSchemeValidation = false; - break; - } + const currentStatechainInfo = statechainInfo.statechain_info[index]; - const currentStatechainInfo = statechainInfo.statechain_info[index]; + const isBlindedMusigSchemeValid = mercury_wasm.verifyBlindedMusigScheme(backupTx, tx0Hex, currentStatechainInfo); - const isBlindedMusigSchemeValid = mercury_wasm.verifyBlindedMusigScheme(backupTx, tx0Hex, currentStatechainInfo); + if (!isBlindedMusigSchemeValid.result) { + console.error(`Invalid musig scheme, ${isBlindedMusigSchemeValid.result.msg}`); + sigSchemeValidation = false; + break; + } - if (!isBlindedMusigSchemeValid.result) { - console.error(`Invalid musig scheme, ${isBlindedMusigSchemeValid.result.msg}`); + if (previousLockTime != null) { + let currentLockTime = mercury_wasm.getBlockheight(backupTx); + if ((previousLockTime - currentLockTime) != serverInfo.interval) { + console.error("interval is not correct"); sigSchemeValidation = false; break; } - - if (previousLockTime != null) { - let currentLockTime = mercury_wasm.getBlockheight(backupTx); - if ((previousLockTime - currentLockTime) != serverInfo.interval) { - console.error("interval is not correct"); - sigSchemeValidation = false; - break; - } - } - - previousLockTime = mercury_wasm.getBlockheight(backupTx); - } - - if (!sigSchemeValidation) { - console.error("Signature scheme validation failed"); - continue; } - const transferReceiverRequestPayload = mercury_wasm.createTransferReceiverRequestPayload(statechainInfo, transferMsg, coin); - - let signedStatechainIdForUnlock = mercury_wasm.signMessage(transferMsg.statechain_id, coin); - - await unlockStatecoin(transferMsg.statechain_id, signedStatechainIdForUnlock, coin.auth_pubkey); + previousLockTime = mercury_wasm.getBlockheight(backupTx); + } - let serverPublicKeyHex = ""; + if (!sigSchemeValidation) { + throw new Error("Signature scheme validation failed"); + } - try { - serverPublicKeyHex = await sendTransferReceiverRequestPayload(transferReceiverRequestPayload); - } catch (error) { - console.error(error); - continue; - } + const transferReceiverRequestPayload = mercury_wasm.createTransferReceiverRequestPayload(statechainInfo, transferMsg, coin); - let newKeyInfo = mercury_wasm.getNewKeyInfo(serverPublicKeyHex, coin, transferMsg.statechain_id, tx0Outpoint, tx0Hex, network); + let signedStatechainIdForUnlock = mercury_wasm.signMessage(transferMsg.statechain_id, coin); - coin.server_pubkey = serverPublicKeyHex; - coin.aggregated_pubkey = newKeyInfo.aggregate_pubkey; - coin.aggregated_address = newKeyInfo.aggregate_address; - coin.statechain_id = transferMsg.statechain_id; - coin.signed_statechain_id = newKeyInfo.signed_statechain_id; - coin.amount = newKeyInfo.amount; - coin.utxo_txid = tx0Outpoint.txid; - coin.utxo_vout = tx0Outpoint.vout; - coin.locktime = previousLockTime; - coin.status = isTx0OutputUnspent.status; + await unlockStatecoin(transferMsg.statechain_id, signedStatechainIdForUnlock, coin.auth_pubkey); - let utxo = `${tx0Outpoint.txid}:${tx0Outpoint.vout}`; + let serverPublicKeyHex = ""; - let activity = { - utxo: utxo, - amount: newKeyInfo.amount, - action: "Receive", - date: new Date().toISOString() - }; + try { + serverPublicKeyHex = await sendTransferReceiverRequestPayload(transferReceiverRequestPayload); + } catch (error) { + throw new Error(error); + } - activities.push(activity); + let newKeyInfo = mercury_wasm.getNewKeyInfo(serverPublicKeyHex, coin, transferMsg.statechain_id, tx0Outpoint, tx0Hex, network); + + coin.server_pubkey = serverPublicKeyHex; + coin.aggregated_pubkey = newKeyInfo.aggregate_pubkey; + coin.aggregated_address = newKeyInfo.aggregate_address; + coin.statechain_id = transferMsg.statechain_id; + coin.signed_statechain_id = newKeyInfo.signed_statechain_id; + coin.amount = newKeyInfo.amount; + coin.utxo_txid = tx0Outpoint.txid; + coin.utxo_vout = tx0Outpoint.vout; + coin.locktime = previousLockTime; + coin.status = isTx0OutputUnspent.status; + + let utxo = `${tx0Outpoint.txid}:${tx0Outpoint.vout}`; + + let activity = { + utxo: utxo, + amount: newKeyInfo.amount, + action: "Receive", + date: new Date().toISOString() + }; - statechain_ids_added.push(transferMsg.statechain_id); + activities.push(activity); - await sqlite_manager.insertOrUpdateBackupTxs(db, transferMsg.statechain_id, transferMsg.backup_transactions); - } + await sqlite_manager.insertOrUpdateBackupTxs(db, transferMsg.statechain_id, transferMsg.backup_transactions); - return statechain_ids_added; + return transferMsg.statechain_id; } const getTx0 = async (electrumClient, tx0_txid) => { diff --git a/clients/nodejs/utils.js b/clients/nodejs/utils.js index 1d3df3ef..358d88bd 100644 --- a/clients/nodejs/utils.js +++ b/clients/nodejs/utils.js @@ -44,7 +44,7 @@ const getNetwork = (wallet_network) => { return bitcoinjs_lib.networks.testnet; case "regtest": return bitcoinjs_lib.networks.regtest; - case "mainnet": + case "bitcoin": return bitcoinjs_lib.networks.bitcoin; default: throw new Error("Unknown network"); diff --git a/clients/rust/src/transfer_receiver.rs b/clients/rust/src/transfer_receiver.rs index d0b12f8f..ebf7dda2 100644 --- a/clients/rust/src/transfer_receiver.rs +++ b/clients/rust/src/transfer_receiver.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, thread, time::Duration}; +use std::{collections::{HashMap, HashSet}, str::FromStr, thread, time::Duration}; use crate::{sqlite_manager::{get_wallet, update_wallet, insert_or_update_backup_txs}, client_config::ClientConfig, utils}; use anyhow::{anyhow, Result}; @@ -28,28 +28,79 @@ pub async fn execute(client_config: &ClientConfig, wallet_name: &str) -> Result< let mut wallet = get_wallet(&client_config.pool, &wallet_name).await?; let info_config = utils::info_config(&client_config).await.unwrap(); - - let mut activities = wallet.activities.as_mut(); - let mut received_statechain_ids = Vec::::new(); + let mut unique_auth_pubkeys: HashSet = HashSet::new(); + + for coin in wallet.coins.iter() { + unique_auth_pubkeys.insert(coin.auth_pubkey.clone()); + } - for coin in wallet.coins.iter_mut() { + let mut enc_msgs_per_auth_pubkey: HashMap> = HashMap::new(); - if coin.status != CoinStatus::INITIALISED { - continue; - } + for auth_pubkey in unique_auth_pubkeys { - let enc_messages = get_msg_addr(&coin.auth_pubkey, &client_config).await?; + let enc_messages = get_msg_addr(&auth_pubkey, &client_config).await?; if enc_messages.len() == 0 { println!("No messages"); continue; } - let statechain_ids_added = process_encrypted_message(client_config, coin, &enc_messages, &wallet.network, &info_config, &mut activities).await?; + enc_msgs_per_auth_pubkey.insert(auth_pubkey.clone(), enc_messages); + } + + let mut received_statechain_ids = Vec::::new(); + + let mut temp_coins = wallet.coins.clone(); + let mut temp_activities = wallet.activities.clone(); + + for (key, values) in &enc_msgs_per_auth_pubkey { + + let auth_pubkey = key.clone(); + + for enc_message in values { + + let coin: Option<&mut Coin> = temp_coins.iter_mut().find(|coin| coin.auth_pubkey == auth_pubkey && coin.status == CoinStatus::INITIALISED); + + if coin.is_some() { + + let mut coin = coin.unwrap(); + + let statechain_id_added = process_encrypted_message(client_config, &mut coin, enc_message, &wallet.network, &info_config, &mut temp_activities).await; + + if statechain_id_added.is_err() { + println!("Error: {}", statechain_id_added.err().unwrap().to_string()); + continue; + } - received_statechain_ids.extend(statechain_ids_added.clone()); + received_statechain_ids.push(statechain_id_added.unwrap()); + + } else { + + let new_coin = mercurylib::transfer::receiver::duplicate_coin_to_initialized_state(&wallet, &auth_pubkey); + + if new_coin.is_err() { + println!("Error: {}", new_coin.err().unwrap().to_string()); + continue; + } + + let mut new_coin = new_coin.unwrap(); + + let statechain_id_added = process_encrypted_message(client_config, &mut new_coin, enc_message, &wallet.network, &info_config, &mut temp_activities).await; + + if statechain_id_added.is_err() { + println!("Error: {}", statechain_id_added.err().unwrap().to_string()); + continue; + } + + temp_coins.push(new_coin); + received_statechain_ids.push(statechain_id_added.unwrap()); + } + } } + wallet.coins = temp_coins.clone(); + wallet.activities = temp_activities.clone(); + update_wallet(&client_config.pool, &wallet).await?; Ok(received_statechain_ids) @@ -69,165 +120,149 @@ async fn get_msg_addr(auth_pubkey: &str, client_config: &ClientConfig) -> Result Ok(response.list_enc_transfer_msg) } -async fn process_encrypted_message(client_config: &ClientConfig, coin: &mut Coin, enc_messages: &Vec, network: &str, info_config: &InfoConfig, activities: &mut Vec) -> Result> { +async fn process_encrypted_message(client_config: &ClientConfig, coin: &mut Coin, enc_message: &str, network: &str, info_config: &InfoConfig, activities: &mut Vec) -> Result { let client_auth_key = coin.auth_privkey.clone(); let new_user_pubkey = coin.user_pubkey.clone(); - let mut statechain_ids_added = Vec::::new(); + let transfer_msg = mercurylib::transfer::receiver::decrypt_transfer_msg(enc_message, &client_auth_key)?; - for enc_message in enc_messages { + let tx0_outpoint = mercurylib::transfer::receiver::get_tx0_outpoint(&transfer_msg.backup_transactions)?; + + let tx0_hex = get_tx0(&client_config.electrum_client, &tx0_outpoint.txid).await?; - let transfer_msg = mercurylib::transfer::receiver::decrypt_transfer_msg(enc_message, &client_auth_key)?; + let is_transfer_signature_valid = verify_transfer_signature(&new_user_pubkey, &tx0_outpoint, &transfer_msg)?; - let tx0_outpoint = mercurylib::transfer::receiver::get_tx0_outpoint(&transfer_msg.backup_transactions)?; - - let tx0_hex = get_tx0(&client_config.electrum_client, &tx0_outpoint.txid).await?; + if !is_transfer_signature_valid { + return Err(anyhow::anyhow!("Invalid transfer signature".to_string())); + } - let is_transfer_signature_valid = verify_transfer_signature(&new_user_pubkey, &tx0_outpoint, &transfer_msg)?; + let statechain_info = utils::get_statechain_info(&transfer_msg.statechain_id, &client_config).await?; - if !is_transfer_signature_valid { - println!("Invalid transfer signature"); - continue; - } + if statechain_info.is_none() { + return Err(anyhow::anyhow!("Statechain info not found".to_string())); + } - let statechain_info = utils::get_statechain_info(&transfer_msg.statechain_id, &client_config).await?; + let statechain_info = statechain_info.unwrap(); - if statechain_info.is_none() { - println!("Statechain info not found"); - continue; - } + let is_tx0_output_pubkey_valid = validate_tx0_output_pubkey(&statechain_info.enclave_public_key, &transfer_msg, &tx0_outpoint, &tx0_hex, network)?; - let statechain_info = statechain_info.unwrap(); + if !is_tx0_output_pubkey_valid { + return Err(anyhow::anyhow!("Invalid tx0 output pubkey".to_string())); + } - let is_tx0_output_pubkey_valid = validate_tx0_output_pubkey(&statechain_info.enclave_public_key, &transfer_msg, &tx0_outpoint, &tx0_hex, network)?; + let latest_backup_tx_pays_to_user_pubkey = verify_latest_backup_tx_pays_to_user_pubkey(&transfer_msg, &new_user_pubkey, network)?; - if !is_tx0_output_pubkey_valid { - println!("Invalid tx0 output pubkey"); - continue; - } + if !latest_backup_tx_pays_to_user_pubkey { + return Err(anyhow::anyhow!("Latest Backup Tx does not pay to the expected public key".to_string())); + } - let latest_backup_tx_pays_to_user_pubkey = verify_latest_backup_tx_pays_to_user_pubkey(&transfer_msg, &new_user_pubkey, network)?; - - if !latest_backup_tx_pays_to_user_pubkey { - println!("Latest Backup Tx does not pay to the expected public key"); - continue; - } + if statechain_info.num_sigs != transfer_msg.backup_transactions.len() as u32 { + return Err(anyhow::anyhow!("num_sigs is not correct".to_string())); + } - if statechain_info.num_sigs != transfer_msg.backup_transactions.len() as u32 { - println!("num_sigs is not correct"); - continue; - } + let (is_tx0_output_unspent, tx0_status) = verify_tx0_output_is_unspent_and_confirmed(&client_config.electrum_client, &tx0_outpoint, &tx0_hex, &network, client_config.confirmation_target).await?; - let (is_tx0_output_unspent, tx0_status) = verify_tx0_output_is_unspent_and_confirmed(&client_config.electrum_client, &tx0_outpoint, &tx0_hex, &network, client_config.confirmation_target).await?; + if !is_tx0_output_unspent { + return Err(anyhow::anyhow!("tx0 output is spent or not confirmed".to_string())); + } - if !is_tx0_output_unspent { - println!("tx0 output is spent or not confirmed."); - continue; - } + let current_fee_rate_sats_per_byte = info_config.fee_rate_sats_per_byte as u32; - let current_fee_rate_sats_per_byte = info_config.fee_rate_sats_per_byte as u32; + let fee_rate_tolerance = client_config.fee_rate_tolerance; - let fee_rate_tolerance = client_config.fee_rate_tolerance; + let mut previous_lock_time: Option = None; - let mut previous_lock_time: Option = None; + let mut sig_scheme_validation = true; - let mut sig_scheme_validation = true; + for (index, backup_tx) in transfer_msg.backup_transactions.iter().enumerate() { - for (index, backup_tx) in transfer_msg.backup_transactions.iter().enumerate() { + let statechain_info = statechain_info.statechain_info.get(index).unwrap(); - let statechain_info = statechain_info.statechain_info.get(index).unwrap(); + let is_signature_valid = verify_transaction_signature(&backup_tx.tx, &tx0_hex, fee_rate_tolerance, current_fee_rate_sats_per_byte); + if is_signature_valid.is_err() { + println!("{}", is_signature_valid.err().unwrap().to_string()); + sig_scheme_validation = false; + break; + } - let is_signature_valid = verify_transaction_signature(&backup_tx.tx, &tx0_hex, fee_rate_tolerance, current_fee_rate_sats_per_byte); - if is_signature_valid.is_err() { - println!("{}", is_signature_valid.err().unwrap().to_string()); - sig_scheme_validation = false; - break; - } + let is_blinded_musig_scheme_valid = verify_blinded_musig_scheme(&backup_tx, &tx0_hex, statechain_info); + if is_blinded_musig_scheme_valid.is_err() { + println!("{}", is_blinded_musig_scheme_valid.err().unwrap().to_string()); + sig_scheme_validation = false; + break; + } - let is_blinded_musig_scheme_valid = verify_blinded_musig_scheme(&backup_tx, &tx0_hex, statechain_info); - if is_blinded_musig_scheme_valid.is_err() { - println!("{}", is_blinded_musig_scheme_valid.err().unwrap().to_string()); + if previous_lock_time.is_some() { + let prev_lock_time = previous_lock_time.unwrap(); + let current_lock_time = get_blockheight(&backup_tx)?; + if (prev_lock_time - current_lock_time) as i32 != info_config.interval as i32 { + println!("interval is not correct"); sig_scheme_validation = false; break; } - - if previous_lock_time.is_some() { - let prev_lock_time = previous_lock_time.unwrap(); - let current_lock_time = get_blockheight(&backup_tx)?; - if (prev_lock_time - current_lock_time) as i32 != info_config.interval as i32 { - println!("interval is not correct"); - sig_scheme_validation = false; - break; - } - } - - previous_lock_time = Some(get_blockheight(&backup_tx)?); } - if !sig_scheme_validation { - println!("Signature scheme validation failed"); - continue; - } + previous_lock_time = Some(get_blockheight(&backup_tx)?); + } - let transfer_receiver_request_payload = create_transfer_receiver_request_payload(&statechain_info, &transfer_msg, &coin)?; - - // unlock the statecoin - it might be part of a batch + if !sig_scheme_validation { + return Err(anyhow::anyhow!("Signature scheme validation failed".to_string())); + } - // the pub_auth_key has not been updated yet in the server (it will be updated after the transfer/receive call) - // So we need to manually sign the statechain_id with the client_auth_key - let signed_statechain_id_for_unlock = sign_message(&transfer_msg.statechain_id, &coin)?; + let transfer_receiver_request_payload = create_transfer_receiver_request_payload(&statechain_info, &transfer_msg, &coin)?; - unlock_statecoin(&client_config, &transfer_msg.statechain_id, &signed_statechain_id_for_unlock, &coin.auth_pubkey).await?; + // unlock the statecoin - it might be part of a batch - // let server_public_key_hex = send_transfer_receiver_request_payload(&client_config, &transfer_receiver_request_payload).await?; + // the pub_auth_key has not been updated yet in the server (it will be updated after the transfer/receive call) + // So we need to manually sign the statechain_id with the client_auth_key + let signed_statechain_id_for_unlock = sign_message(&transfer_msg.statechain_id, &coin)?; - let transfer_receiver_result = send_transfer_receiver_request_payload(&client_config, &transfer_receiver_request_payload).await; + unlock_statecoin(&client_config, &transfer_msg.statechain_id, &signed_statechain_id_for_unlock, &coin.auth_pubkey).await?; - let server_public_key_hex = match transfer_receiver_result { - Ok(server_public_key_hex) => server_public_key_hex, - Err(err) => { - println!("Error: {}", err.to_string()); - continue; - } - }; - - let new_key_info = get_new_key_info(&server_public_key_hex, &coin, &transfer_msg.statechain_id, &tx0_outpoint, &tx0_hex, network)?; + // let server_public_key_hex = send_transfer_receiver_request_payload(&client_config, &transfer_receiver_request_payload).await?; - if previous_lock_time.is_none() { - println!("previous_lock_time is None"); - continue; - } + let transfer_receiver_result = send_transfer_receiver_request_payload(&client_config, &transfer_receiver_request_payload).await; - coin.server_pubkey = Some(server_public_key_hex); - coin.aggregated_pubkey = Some(new_key_info.aggregate_pubkey); - coin.aggregated_address = Some(new_key_info.aggregate_address); - coin.statechain_id = Some(transfer_msg.statechain_id.clone()); - coin.signed_statechain_id = Some(new_key_info.signed_statechain_id.clone()); - coin.amount = Some(new_key_info.amount); - coin.utxo_txid = Some(tx0_outpoint.txid.clone()); - coin.utxo_vout = Some(tx0_outpoint.vout); - coin.locktime = Some(previous_lock_time.unwrap()); - coin.status = tx0_status; + let server_public_key_hex = match transfer_receiver_result { + Ok(server_public_key_hex) => server_public_key_hex, + Err(err) => { + return Err(anyhow::anyhow!("Error: {}", err.to_string())); + } + }; - let date = Utc::now(); // This will get the current date and time in UTC - let iso_string = date.to_rfc3339(); // Converts the date to an ISO 8601 string + let new_key_info = get_new_key_info(&server_public_key_hex, &coin, &transfer_msg.statechain_id, &tx0_outpoint, &tx0_hex, network)?; - let activity = Activity { - utxo: tx0_outpoint.txid.clone(), - amount: new_key_info.amount, - action: "Receive".to_string(), - date: iso_string - }; + if previous_lock_time.is_none() { + return Err(anyhow::anyhow!("previous_lock_time is None".to_string())); + } - activities.push(activity); + coin.server_pubkey = Some(server_public_key_hex); + coin.aggregated_pubkey = Some(new_key_info.aggregate_pubkey); + coin.aggregated_address = Some(new_key_info.aggregate_address); + coin.statechain_id = Some(transfer_msg.statechain_id.clone()); + coin.signed_statechain_id = Some(new_key_info.signed_statechain_id.clone()); + coin.amount = Some(new_key_info.amount); + coin.utxo_txid = Some(tx0_outpoint.txid.clone()); + coin.utxo_vout = Some(tx0_outpoint.vout); + coin.locktime = Some(previous_lock_time.unwrap()); + coin.status = tx0_status; + + let date = Utc::now(); // This will get the current date and time in UTC + let iso_string = date.to_rfc3339(); // Converts the date to an ISO 8601 string + + let activity = Activity { + utxo: tx0_outpoint.txid.clone(), + amount: new_key_info.amount, + action: "Receive".to_string(), + date: iso_string + }; - statechain_ids_added.push(transfer_msg.statechain_id.clone()); + activities.push(activity); - insert_or_update_backup_txs(&client_config.pool, &transfer_msg.statechain_id, &transfer_msg.backup_transactions).await?; - } + insert_or_update_backup_txs(&client_config.pool, &transfer_msg.statechain_id, &transfer_msg.backup_transactions).await?; - Ok(statechain_ids_added) + Ok(transfer_msg.statechain_id.clone()) } async fn get_tx0(electrum_client: &electrum_client::Client, tx0_txid: &str) -> Result { diff --git a/lib/src/error.rs b/lib/src/error.rs index 4c52caee..9ffee630 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -78,6 +78,7 @@ pub enum MercuryError { T1MustBeExactly32BytesError, NoX1Pub, NoAggregatedPubkeyError, + CoinNotFound, } impl core::fmt::Display for MercuryError { diff --git a/lib/src/transfer/receiver.rs b/lib/src/transfer/receiver.rs index 0eac4daf..3768fa96 100644 --- a/lib/src/transfer/receiver.rs +++ b/lib/src/transfer/receiver.rs @@ -4,7 +4,7 @@ use bitcoin::{PrivateKey, Transaction, hashes::{sha256, Hash}, Txid, Address, si use secp256k1_zkp::{PublicKey, schnorr::Signature, Secp256k1, Message, XOnlyPublicKey, musig::{MusigPubNonce, BlindingFactor, blinded_musig_pubkey_xonly_tweak_add, MusigAggNonce, MusigSession}, SecretKey, Scalar, KeyPair}; use serde::{Serialize, Deserialize}; -use crate::{error::MercuryError, utils::get_network, wallet::{BackupTx, Coin}}; +use crate::{error::MercuryError, utils::get_network, wallet::{BackupTx, Coin, CoinStatus, Wallet}}; use super::TransferMsg; @@ -93,6 +93,47 @@ pub struct NewKeyInfo { pub amount: u32, } +#[cfg_attr(feature = "bindings", uniffi::export)] +pub fn duplicate_coin_to_initialized_state(wallet: &Wallet, auth_pubkey: &str) -> Result { + // wallet.coins.iter().filter(|coin| coin.status == CoinStatus::INITIALISED).cloned().collect() + let coin = wallet.coins.iter().find(|coin| coin.auth_pubkey == auth_pubkey.to_string()); + + if coin.is_none() { + return Err(MercuryError::CoinNotFound); + } + + let coin = coin.unwrap(); + + Ok(Coin { + index: coin.index, + user_privkey: coin.user_privkey.clone(), + user_pubkey: coin.user_pubkey.clone(), + auth_privkey: coin.auth_privkey.clone(), + auth_pubkey: coin.auth_pubkey.clone(), + derivation_path: coin.derivation_path.clone(), + fingerprint: coin.fingerprint.clone(), + address: coin.address.clone(), + backup_address: coin. backup_address.clone(), + server_pubkey: None, + aggregated_pubkey: None, + aggregated_address: None, + utxo_txid: None, + utxo_vout: None, + amount: None, + statechain_id: None, + signed_statechain_id: None, + locktime: None, + secret_nonce: None, + public_nonce: None, + blinding_factor: None, + server_public_nonce: None, + tx_cpfp: None, + tx_withdraw: None, + withdrawal_address: None, + status: CoinStatus::INITIALISED, + }) +} + pub fn decrypt_transfer_msg(encrypted_message: &str, private_key_wif: &str) -> Result { let client_auth_key = PrivateKey::from_wif(private_key_wif)?.inner; diff --git a/server/migrations/0001_key_data_table.sql b/server/migrations/0001_key_data_table.sql index c8b1b849..276fd15e 100644 --- a/server/migrations/0001_key_data_table.sql +++ b/server/migrations/0001_key_data_table.sql @@ -1,7 +1,7 @@ CREATE TABLE public.statechain_data ( id serial4 NOT NULL, token_id varchar NULL UNIQUE, - auth_xonly_public_key bytea NULL UNIQUE, + auth_xonly_public_key bytea NULL, server_public_key bytea NULL UNIQUE, statechain_id varchar NULL UNIQUE, CONSTRAINT statechain_data_pkey PRIMARY KEY (id), diff --git a/server/src/database/deposit.rs b/server/src/database/deposit.rs new file mode 100644 index 00000000..151ea00c --- /dev/null +++ b/server/src/database/deposit.rs @@ -0,0 +1,91 @@ +use secp256k1_zkp::{PublicKey, XOnlyPublicKey}; +use sqlx::Row; + +pub async fn get_token_status(pool: &sqlx::PgPool, token_id: &str) -> Option { + + let row = sqlx::query( + "SELECT confirmed, spent \ + FROM public.tokens \ + WHERE token_id = $1") + .bind(&token_id) + .fetch_one(pool) + .await; + + if row.is_err() { + match row.err().unwrap() { + sqlx::Error::RowNotFound => return None, + _ => return None, // this case should be treated as unexpected error + } + } + + let row = row.unwrap(); + + let confirmed: bool = row.get(0); + let spent: bool = row.get(1); + if confirmed && !spent { + return Some(true); + } else { + return Some(false); + } + +} + +pub async fn set_token_spent(pool: &sqlx::PgPool, token_id: &str) { + + let mut transaction = pool.begin().await.unwrap(); + + let query = "UPDATE tokens \ + SET spent = true \ + WHERE token_id = $1"; + + let _ = sqlx::query(query) + .bind(token_id) + .execute(&mut *transaction) + .await + .unwrap(); + + transaction.commit().await.unwrap(); +} + +pub async fn check_existing_key(pool: &sqlx::PgPool, auth_key: &XOnlyPublicKey) -> bool { + let row = sqlx::query( + "SELECT 1 \ + FROM statechain_data \ + WHERE auth_xonly_public_key = $1") + .bind(&auth_key.serialize()) + .fetch_one(pool) + .await; + + match row { + Ok(_) => true, + Err(sqlx::Error::RowNotFound) => false, + Err(_) => false, + } +} + +pub async fn insert_new_deposit(pool: &sqlx::PgPool, token_id: &str, auth_key: &XOnlyPublicKey, server_public_key: &PublicKey, statechain_id: &String) { + + let query = "INSERT INTO statechain_data (token_id, auth_xonly_public_key, server_public_key, statechain_id) VALUES ($1, $2, $3, $4)"; + + let _ = sqlx::query(query) + .bind(token_id) + .bind(&auth_key.serialize()) + .bind(&server_public_key.serialize()) + .bind(statechain_id) + .execute(pool) + .await + .unwrap(); +} + +pub async fn insert_new_token(pool: &sqlx::PgPool, token_id: &str) { + + let query = "INSERT INTO tokens (token_id, confirmed, spent) VALUES ($1, $2, $3)"; + + let _ = sqlx::query(query) + .bind(token_id) + .bind(true) + .bind(false) + .execute(pool) + .await + .unwrap(); +} diff --git a/server/src/database/mod.rs b/server/src/database/mod.rs index 5764bcf6..1b064ecd 100644 --- a/server/src/database/mod.rs +++ b/server/src/database/mod.rs @@ -1,3 +1,4 @@ pub mod transfer_sender; pub mod transfer_receiver; -pub mod transfer; \ No newline at end of file +pub mod transfer; +pub mod deposit; diff --git a/server/src/endpoints/deposit.rs b/server/src/endpoints/deposit.rs index 70cbe071..aff61f94 100644 --- a/server/src/endpoints/deposit.rs +++ b/server/src/endpoints/deposit.rs @@ -5,88 +5,8 @@ use rocket::{serde::json::Json, response::status, State, http::Status}; use secp256k1_zkp::{XOnlyPublicKey, schnorr::Signature, Message, Secp256k1, PublicKey}; use serde::{Serialize, Deserialize}; use serde_json::{Value, json}; -use sqlx::Row; - use crate::server::StateChainEntity; - -pub async fn get_token_status(pool: &sqlx::PgPool, token_id: &str) -> Option { - - let row = sqlx::query( - "SELECT confirmed, spent \ - FROM public.tokens \ - WHERE token_id = $1") - .bind(&token_id) - .fetch_one(pool) - .await; - - if row.is_err() { - match row.err().unwrap() { - sqlx::Error::RowNotFound => return None, - _ => return None, // this case should be treated as unexpected error - } - } - - let row = row.unwrap(); - - let confirmed: bool = row.get(0); - let spent: bool = row.get(1); - if confirmed && !spent { - return Some(true); - } else { - return Some(false); - } - -} - -pub async fn set_token_spent(pool: &sqlx::PgPool, token_id: &str) { - - let mut transaction = pool.begin().await.unwrap(); - - let query = "UPDATE tokens \ - SET spent = true \ - WHERE token_id = $1"; - - let _ = sqlx::query(query) - .bind(token_id) - .execute(&mut *transaction) - .await - .unwrap(); - - transaction.commit().await.unwrap(); -} - -pub async fn check_existing_key(pool: &sqlx::PgPool, auth_key: &XOnlyPublicKey) -> Option { - - let row = sqlx::query( - "SELECT statechain_id, server_public_key \ - FROM statechain_data \ - WHERE auth_xonly_public_key = $1") - .bind(&auth_key.serialize()) - .fetch_one(pool) - .await; - - if row.is_err() { - match row.err().unwrap() { - sqlx::Error::RowNotFound => return None, - _ => return None - } - } - - let row_ur = row.unwrap(); - - let server_public_key_bytes = row_ur.get::, _>(1); - let server_pubkey = PublicKey::from_slice(&server_public_key_bytes).unwrap(); - - let deposit_msg1_response = mercurylib::deposit::DepositMsg1Response { - server_pubkey: server_pubkey.to_string(), - statechain_id: row_ur.get(0), - }; - - return Some(deposit_msg1_response); - -} - #[get("/deposit/get_token")] pub async fn get_token(statechain_entity: &State) -> status::Custom> { @@ -101,7 +21,7 @@ pub async fn get_token(statechain_entity: &State) -> status::C let token_id = uuid::Uuid::new_v4().to_string(); - insert_new_token(&statechain_entity.pool, &token_id).await; + crate::database::deposit::insert_new_token(&statechain_entity.pool, &token_id).await; let token = mercurylib::deposit::TokenID { token_id @@ -133,7 +53,7 @@ pub async fn token_init(statechain_entity: &State) -> status:: let spent = false; let expiry = String::from("2024-12-26T17:29:50.013Z"); - insert_new_token(&statechain_entity.pool, &token_id).await; + crate::database::deposit::insert_new_token(&statechain_entity.pool, &token_id).await; let token = mercurylib::wallet::Token { btc_payment_address, @@ -166,7 +86,6 @@ pub async fn post_deposit(statechain_entity: &State, deposit_m if !secp.verify_schnorr(&signed_token_id, &msg, &auth_key).is_ok() { let response_body = json!({ - "error": "Internal Server Error", "message": "Signature does not match authentication key." }); @@ -174,15 +93,17 @@ pub async fn post_deposit(statechain_entity: &State, deposit_m } - let existing_key = check_existing_key(&statechain_entity.pool, &auth_key).await; + let is_existing_key = crate::database::deposit::check_existing_key(&statechain_entity.pool, &auth_key).await; - if !existing_key.is_none() { - let response_body = json!(existing_key.unwrap()); + if is_existing_key { + let response_body = json!({ + "message": "The authentication key is already assigned to a statecoin." + }); - return status::Custom(Status::Ok, Json(response_body)); + return status::Custom(Status::BadRequest, Json(response_body)); } - let valid_token = get_token_status(&statechain_entity.pool, &token_id).await; + let valid_token = crate::database::deposit::get_token_status(&statechain_entity.pool, &token_id).await; if valid_token.is_none() { let response_body = json!({ @@ -249,9 +170,9 @@ pub async fn post_deposit(statechain_entity: &State, deposit_m let server_pubkey = PublicKey::from_str(&server_pubkey_hex).unwrap(); - insert_new_deposit(&statechain_entity.pool, &token_id, &auth_key, &server_pubkey, &statechain_id).await; + crate::database::deposit::insert_new_deposit(&statechain_entity.pool, &token_id, &auth_key, &server_pubkey, &statechain_id).await; - set_token_spent(&statechain_entity.pool, &token_id).await; + crate::database::deposit::set_token_spent(&statechain_entity.pool, &token_id).await; let deposit_msg1_response = mercurylib::deposit::DepositMsg1Response { server_pubkey: server_pubkey.to_string(), @@ -262,30 +183,3 @@ pub async fn post_deposit(statechain_entity: &State, deposit_m status::Custom(Status::Ok, Json(response_body)) } - -pub async fn insert_new_deposit(pool: &sqlx::PgPool, token_id: &str, auth_key: &XOnlyPublicKey, server_public_key: &PublicKey, statechain_id: &String) { - - let query = "INSERT INTO statechain_data (token_id, auth_xonly_public_key, server_public_key, statechain_id) VALUES ($1, $2, $3, $4)"; - - let _ = sqlx::query(query) - .bind(token_id) - .bind(&auth_key.serialize()) - .bind(&server_public_key.serialize()) - .bind(statechain_id) - .execute(pool) - .await - .unwrap(); -} - -pub async fn insert_new_token(pool: &sqlx::PgPool, token_id: &str) { - - let query = "INSERT INTO tokens (token_id, confirmed, spent) VALUES ($1, $2, $3)"; - - let _ = sqlx::query(query) - .bind(token_id) - .bind(true) - .bind(false) - .execute(pool) - .await - .unwrap(); -} diff --git a/wasm/node_pkg/debug/mercury_wasm.d.ts b/wasm/node_pkg/debug/mercury_wasm.d.ts index 3ef8c7d6..50ac7538 100644 --- a/wasm/node_pkg/debug/mercury_wasm.d.ts +++ b/wasm/node_pkg/debug/mercury_wasm.d.ts @@ -258,6 +258,12 @@ export function isEnclavePubkeyPartOfCoin(coin: any, enclave_pubkey: string): bo */ export function latestBackuptxPaysToUserpubkey(backup_transactions: any, coin: any, network: string): any; /** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +export function duplicateCoinToInitializedState(walletJson: any, authPubkey: string): any; +/** * @returns {any} */ export function getMockWallet(): any; diff --git a/wasm/node_pkg/debug/mercury_wasm.js b/wasm/node_pkg/debug/mercury_wasm.js index 8b8d7441..bd438418 100644 --- a/wasm/node_pkg/debug/mercury_wasm.js +++ b/wasm/node_pkg/debug/mercury_wasm.js @@ -832,6 +832,18 @@ module.exports.latestBackuptxPaysToUserpubkey = function(backup_transactions, co return takeObject(ret); }; +/** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +module.exports.duplicateCoinToInitializedState = function(walletJson, authPubkey) { + const ptr0 = passStringToWasm0(authPubkey, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.duplicateCoinToInitializedState(addHeapObject(walletJson), ptr0, len0); + return takeObject(ret); +}; + /** * @returns {any} */ diff --git a/wasm/node_pkg/debug/mercury_wasm_bg.wasm b/wasm/node_pkg/debug/mercury_wasm_bg.wasm index 27387e63..ae2d29d0 100644 Binary files a/wasm/node_pkg/debug/mercury_wasm_bg.wasm and b/wasm/node_pkg/debug/mercury_wasm_bg.wasm differ diff --git a/wasm/node_pkg/debug/mercury_wasm_bg.wasm.d.ts b/wasm/node_pkg/debug/mercury_wasm_bg.wasm.d.ts index b1a3f9fb..636e92b7 100644 --- a/wasm/node_pkg/debug/mercury_wasm_bg.wasm.d.ts +++ b/wasm/node_pkg/debug/mercury_wasm_bg.wasm.d.ts @@ -40,6 +40,7 @@ export function validateAddress(a: number, b: number, c: number, d: number): num export function signMessage(a: number, b: number, c: number, d: number): void; export function isEnclavePubkeyPartOfCoin(a: number, b: number, c: number): number; export function latestBackuptxPaysToUserpubkey(a: number, b: number, c: number, d: number): number; +export function duplicateCoinToInitializedState(a: number, b: number, c: number): number; export function getMockWallet(): number; export function rustsecp256k1zkp_v0_8_1_default_illegal_callback_fn(a: number, b: number): void; export function rustsecp256k1zkp_v0_8_1_default_error_callback_fn(a: number, b: number): void; diff --git a/wasm/node_pkg/release/mercury_wasm.d.ts b/wasm/node_pkg/release/mercury_wasm.d.ts index 3ef8c7d6..50ac7538 100644 --- a/wasm/node_pkg/release/mercury_wasm.d.ts +++ b/wasm/node_pkg/release/mercury_wasm.d.ts @@ -258,6 +258,12 @@ export function isEnclavePubkeyPartOfCoin(coin: any, enclave_pubkey: string): bo */ export function latestBackuptxPaysToUserpubkey(backup_transactions: any, coin: any, network: string): any; /** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +export function duplicateCoinToInitializedState(walletJson: any, authPubkey: string): any; +/** * @returns {any} */ export function getMockWallet(): any; diff --git a/wasm/node_pkg/release/mercury_wasm.js b/wasm/node_pkg/release/mercury_wasm.js index d0ce59dd..477b2f8e 100644 --- a/wasm/node_pkg/release/mercury_wasm.js +++ b/wasm/node_pkg/release/mercury_wasm.js @@ -806,6 +806,18 @@ module.exports.latestBackuptxPaysToUserpubkey = function(backup_transactions, co return takeObject(ret); }; +/** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +module.exports.duplicateCoinToInitializedState = function(walletJson, authPubkey) { + const ptr0 = passStringToWasm0(authPubkey, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.duplicateCoinToInitializedState(addHeapObject(walletJson), ptr0, len0); + return takeObject(ret); +}; + /** * @returns {any} */ diff --git a/wasm/node_pkg/release/mercury_wasm_bg.wasm b/wasm/node_pkg/release/mercury_wasm_bg.wasm index 8fc93007..ad502e99 100644 Binary files a/wasm/node_pkg/release/mercury_wasm_bg.wasm and b/wasm/node_pkg/release/mercury_wasm_bg.wasm differ diff --git a/wasm/node_pkg/release/mercury_wasm_bg.wasm.d.ts b/wasm/node_pkg/release/mercury_wasm_bg.wasm.d.ts index b1a3f9fb..636e92b7 100644 --- a/wasm/node_pkg/release/mercury_wasm_bg.wasm.d.ts +++ b/wasm/node_pkg/release/mercury_wasm_bg.wasm.d.ts @@ -40,6 +40,7 @@ export function validateAddress(a: number, b: number, c: number, d: number): num export function signMessage(a: number, b: number, c: number, d: number): void; export function isEnclavePubkeyPartOfCoin(a: number, b: number, c: number): number; export function latestBackuptxPaysToUserpubkey(a: number, b: number, c: number, d: number): number; +export function duplicateCoinToInitializedState(a: number, b: number, c: number): number; export function getMockWallet(): number; export function rustsecp256k1zkp_v0_8_1_default_illegal_callback_fn(a: number, b: number): void; export function rustsecp256k1zkp_v0_8_1_default_error_callback_fn(a: number, b: number): void; diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index bb2d17cd..089cba2e 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -130,7 +130,7 @@ pub fn fromMnemonic(name: String, mnemonic: String) -> JsValue { tutorials: false }; - let mut wallet = Wallet { + let wallet = Wallet { name, mnemonic, version: String::from("0.1.0"), @@ -454,6 +454,18 @@ pub fn latestBackuptxPaysToUserpubkey(backup_transactions: JsValue, coin: JsValu } } +#[wasm_bindgen] +pub fn duplicateCoinToInitializedState(walletJson: JsValue, authPubkey: String) -> JsValue { + let wallet: Wallet = serde_wasm_bindgen::from_value(walletJson).unwrap(); + let new_coin = mercurylib::transfer::receiver::duplicate_coin_to_initialized_state(&wallet, &authPubkey); + + if new_coin.is_err() { + return JsValue::NULL; + } else { + serde_wasm_bindgen::to_value(&new_coin.unwrap()).unwrap() + } +} + #[wasm_bindgen] pub fn getMockWallet() -> JsValue { let tokens = vec![ diff --git a/wasm/web_pkg/debug/mercury_wasm.d.ts b/wasm/web_pkg/debug/mercury_wasm.d.ts index 6970dd74..44eb7a67 100644 --- a/wasm/web_pkg/debug/mercury_wasm.d.ts +++ b/wasm/web_pkg/debug/mercury_wasm.d.ts @@ -258,6 +258,12 @@ export function isEnclavePubkeyPartOfCoin(coin: any, enclave_pubkey: string): bo */ export function latestBackuptxPaysToUserpubkey(backup_transactions: any, coin: any, network: string): any; /** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +export function duplicateCoinToInitializedState(walletJson: any, authPubkey: string): any; +/** * @returns {any} */ export function getMockWallet(): any; @@ -305,6 +311,7 @@ export interface InitOutput { readonly signMessage: (a: number, b: number, c: number, d: number) => void; readonly isEnclavePubkeyPartOfCoin: (a: number, b: number, c: number) => number; readonly latestBackuptxPaysToUserpubkey: (a: number, b: number, c: number, d: number) => number; + readonly duplicateCoinToInitializedState: (a: number, b: number, c: number) => number; readonly getMockWallet: () => number; readonly rustsecp256k1zkp_v0_8_1_default_illegal_callback_fn: (a: number, b: number) => void; readonly rustsecp256k1zkp_v0_8_1_default_error_callback_fn: (a: number, b: number) => void; diff --git a/wasm/web_pkg/debug/mercury_wasm.js b/wasm/web_pkg/debug/mercury_wasm.js index 9b722f5e..6a8dcb3d 100644 --- a/wasm/web_pkg/debug/mercury_wasm.js +++ b/wasm/web_pkg/debug/mercury_wasm.js @@ -829,6 +829,18 @@ export function latestBackuptxPaysToUserpubkey(backup_transactions, coin, networ return takeObject(ret); } +/** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +export function duplicateCoinToInitializedState(walletJson, authPubkey) { + const ptr0 = passStringToWasm0(authPubkey, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.duplicateCoinToInitializedState(addHeapObject(walletJson), ptr0, len0); + return takeObject(ret); +} + /** * @returns {any} */ diff --git a/wasm/web_pkg/debug/mercury_wasm_bg.wasm b/wasm/web_pkg/debug/mercury_wasm_bg.wasm index 4baa688f..768ea079 100644 Binary files a/wasm/web_pkg/debug/mercury_wasm_bg.wasm and b/wasm/web_pkg/debug/mercury_wasm_bg.wasm differ diff --git a/wasm/web_pkg/debug/mercury_wasm_bg.wasm.d.ts b/wasm/web_pkg/debug/mercury_wasm_bg.wasm.d.ts index b1a3f9fb..636e92b7 100644 --- a/wasm/web_pkg/debug/mercury_wasm_bg.wasm.d.ts +++ b/wasm/web_pkg/debug/mercury_wasm_bg.wasm.d.ts @@ -40,6 +40,7 @@ export function validateAddress(a: number, b: number, c: number, d: number): num export function signMessage(a: number, b: number, c: number, d: number): void; export function isEnclavePubkeyPartOfCoin(a: number, b: number, c: number): number; export function latestBackuptxPaysToUserpubkey(a: number, b: number, c: number, d: number): number; +export function duplicateCoinToInitializedState(a: number, b: number, c: number): number; export function getMockWallet(): number; export function rustsecp256k1zkp_v0_8_1_default_illegal_callback_fn(a: number, b: number): void; export function rustsecp256k1zkp_v0_8_1_default_error_callback_fn(a: number, b: number): void; diff --git a/wasm/web_pkg/release/mercury_wasm.d.ts b/wasm/web_pkg/release/mercury_wasm.d.ts index 6970dd74..44eb7a67 100644 --- a/wasm/web_pkg/release/mercury_wasm.d.ts +++ b/wasm/web_pkg/release/mercury_wasm.d.ts @@ -258,6 +258,12 @@ export function isEnclavePubkeyPartOfCoin(coin: any, enclave_pubkey: string): bo */ export function latestBackuptxPaysToUserpubkey(backup_transactions: any, coin: any, network: string): any; /** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +export function duplicateCoinToInitializedState(walletJson: any, authPubkey: string): any; +/** * @returns {any} */ export function getMockWallet(): any; @@ -305,6 +311,7 @@ export interface InitOutput { readonly signMessage: (a: number, b: number, c: number, d: number) => void; readonly isEnclavePubkeyPartOfCoin: (a: number, b: number, c: number) => number; readonly latestBackuptxPaysToUserpubkey: (a: number, b: number, c: number, d: number) => number; + readonly duplicateCoinToInitializedState: (a: number, b: number, c: number) => number; readonly getMockWallet: () => number; readonly rustsecp256k1zkp_v0_8_1_default_illegal_callback_fn: (a: number, b: number) => void; readonly rustsecp256k1zkp_v0_8_1_default_error_callback_fn: (a: number, b: number) => void; diff --git a/wasm/web_pkg/release/mercury_wasm.js b/wasm/web_pkg/release/mercury_wasm.js index bd4b3e60..98ba3e4c 100644 --- a/wasm/web_pkg/release/mercury_wasm.js +++ b/wasm/web_pkg/release/mercury_wasm.js @@ -803,6 +803,18 @@ export function latestBackuptxPaysToUserpubkey(backup_transactions, coin, networ return takeObject(ret); } +/** +* @param {any} walletJson +* @param {string} authPubkey +* @returns {any} +*/ +export function duplicateCoinToInitializedState(walletJson, authPubkey) { + const ptr0 = passStringToWasm0(authPubkey, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.duplicateCoinToInitializedState(addHeapObject(walletJson), ptr0, len0); + return takeObject(ret); +} + /** * @returns {any} */ diff --git a/wasm/web_pkg/release/mercury_wasm_bg.wasm b/wasm/web_pkg/release/mercury_wasm_bg.wasm index a35151bc..e0ee0a80 100644 Binary files a/wasm/web_pkg/release/mercury_wasm_bg.wasm and b/wasm/web_pkg/release/mercury_wasm_bg.wasm differ diff --git a/wasm/web_pkg/release/mercury_wasm_bg.wasm.d.ts b/wasm/web_pkg/release/mercury_wasm_bg.wasm.d.ts index b1a3f9fb..636e92b7 100644 --- a/wasm/web_pkg/release/mercury_wasm_bg.wasm.d.ts +++ b/wasm/web_pkg/release/mercury_wasm_bg.wasm.d.ts @@ -40,6 +40,7 @@ export function validateAddress(a: number, b: number, c: number, d: number): num export function signMessage(a: number, b: number, c: number, d: number): void; export function isEnclavePubkeyPartOfCoin(a: number, b: number, c: number): number; export function latestBackuptxPaysToUserpubkey(a: number, b: number, c: number, d: number): number; +export function duplicateCoinToInitializedState(a: number, b: number, c: number): number; export function getMockWallet(): number; export function rustsecp256k1zkp_v0_8_1_default_illegal_callback_fn(a: number, b: number): void; export function rustsecp256k1zkp_v0_8_1_default_error_callback_fn(a: number, b: number): void;