// Copyright 2021 The Grin Develope; // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Generic implementation of owner API functions use uuid::Uuid; use crate::api_impl::foreign::finalize_tx as foreign_finalize; use crate::contract::proofs::{InvoiceProof, ProofWitness}; use crate::grin_core::core::hash::Hashed; use crate::grin_core::core::{Output, OutputFeatures, Transaction}; use crate::grin_core::libtx::proof; use crate::grin_keychain::ViewKey; use crate::grin_util::secp::key::SecretKey; use crate::grin_util::secp::pedersen; use crate::grin_util::{static_secp_instance, Mutex, ToHex}; use crate::util::{OnionV3Address, OnionV3AddressError}; use crate::api_impl::owner_updater::StatusMessage; use crate::contract::types::{ContractNewArgsAPI, ContractRevokeArgsAPI, ContractSetupArgsAPI}; use crate::grin_keychain::{BlindingFactor, Identifier, Keychain, SwitchCommitmentType}; use crate::mwixnet::onion::create_onion; use crate::mwixnet::types::{ add_excess, new_hop, random_secret, ComSignature, Hop, MixnetReqCreationParams, SwapReq, }; use crate::internal::{keys, scan, selection, tx, updater}; use crate::slate::{PaymentInfo, Slate, SlateState}; use crate::types::{AcctPathMapping, NodeClient, TxLogEntry, WalletBackend, WalletInfo}; use crate::Error; use crate::{ address, contract, wallet_lock, BuiltOutput, InitTxArgs, IssueInvoiceTxArgs, NodeHeightResult, OutputCommitMapping, PaymentProof, RetrieveTxQueryArgs, ScannedBlockInfo, Slatepack, SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletInitStatus, WalletInst, WalletLCProvider, }; use chrono::prelude::{DateTime, NaiveDateTime, Utc}; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::SecretKey as DalekSecretKey; use ed25519_dalek::Verifier; use std::convert::{TryFrom, TryInto}; use std::sync::mpsc::Sender; use std::sync::Arc; /// List of accounts pub fn accounts<'a, T: ?Sized, C, K>(w: &mut T) -> Result, Error> where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { keys::accounts(&mut *w) } /// new account path pub fn create_account_path<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, label: &str, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { keys::new_acct_path(&mut *w, keychain_mask, label) } /// set active account pub fn set_active_account<'a, T: ?Sized, C, K>(w: &mut T, label: &str) -> Result<(), Error> where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { w.set_parent_key_id_by_name(label) } /// Hash of the wallet root public key pub fn get_rewind_hash<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { wallet_lock!(wallet_inst, w); let keychain = w.keychain(keychain_mask)?; let root_public_key = keychain.public_root_key(); let rewind_hash = ViewKey::rewind_hash(keychain.secp(), root_public_key).to_hex(); Ok(rewind_hash) } /// Retrieve the slatepack address for the current parent key at /// the given index pub fn get_slatepack_address<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, index: u32, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); let k = w.keychain(keychain_mask)?; let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, index)?; SlatepackAddress::try_from(&sec_addr_key) } /// Retrieve the decryption key for the current parent key /// the given index pub fn get_slatepack_secret_key<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, index: u32, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); let k = w.keychain(keychain_mask)?; let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, index)?; let d_skey = match DalekSecretKey::from_bytes(&sec_addr_key.0) { Ok(k) => k, Err(e) => { return Err(OnionV3AddressError::InvalidPrivateKey(format!( "Unable to create secret key: {}", e )) .into()); } }; Ok(d_skey) } /// Create a slatepack message from the given slate pub fn create_slatepack_message<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, slate: &Slate, sender_index: Option, recipients: Vec, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let sender = match sender_index { Some(i) => Some(get_slatepack_address(wallet_inst, keychain_mask, i)?), None => None, }; let packer = Slatepacker::new(SlatepackerArgs { sender, recipients, dec_key: None, }); let slatepack = packer.create_slatepack(slate)?; packer.armor_slatepack(&slatepack) } /// Unpack a slate from the given slatepack message, /// optionally decrypting pub fn slate_from_slatepack_message<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, slatepack: String, secret_indices: Vec, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { if secret_indices.is_empty() { let packer = Slatepacker::new(SlatepackerArgs { sender: None, recipients: vec![], dec_key: None, }); let slatepack = packer.deser_slatepack(slatepack.as_bytes(), true)?; return packer.get_slate(&slatepack); } else { for index in secret_indices { let dec_key = Some(get_slatepack_secret_key( wallet_inst.clone(), keychain_mask, index, )?); let packer = Slatepacker::new(SlatepackerArgs { sender: None, recipients: vec![], dec_key: (&dec_key).as_ref(), }); let res = packer.deser_slatepack(slatepack.as_bytes(), true); let slatepack = match res { Ok(sp) => sp, Err(_) => { continue; } }; return packer.get_slate(&slatepack); } return Err(Error::SlatepackDecryption( "Could not decrypt slatepack with any provided index on the address derivation path" .to_owned(), ) .into()); } } /// Decode a slatepack message, to allow viewing /// Will decrypt if possible, otherwise will return /// undecrypted slatepack pub fn decode_slatepack_message<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, slatepack: String, secret_indices: Vec, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let packer = Slatepacker::new(SlatepackerArgs { sender: None, recipients: vec![], dec_key: None, }); if secret_indices.is_empty() { packer.deser_slatepack(slatepack.as_bytes(), false) } else { for index in secret_indices { let dec_key = Some(get_slatepack_secret_key( wallet_inst.clone(), keychain_mask, index, )?); let packer = Slatepacker::new(SlatepackerArgs { sender: None, recipients: vec![], dec_key: (&dec_key).as_ref(), }); let res = packer.deser_slatepack(slatepack.as_bytes(), true); let slatepack = match res { Ok(sp) => sp, Err(_) => { continue; } }; return Ok(slatepack); } packer.deser_slatepack(slatepack.as_bytes(), false) } } /// retrieve outputs pub fn retrieve_outputs<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, include_spent: bool, refresh_from_node: bool, tx_id: Option, ) -> Result<(bool, Vec), Error> where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let validated = if refresh_from_node { update_wallet_state( wallet_inst.clone(), keychain_mask, status_send_channel, false, )? } else { false }; wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); Ok(( validated, updater::retrieve_outputs( &mut **w, keychain_mask, include_spent, tx_id, Some(&parent_key_id), )?, )) } /// Retrieve txs pub fn retrieve_txs<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, query_args: Option, ) -> Result<(bool, Vec), Error> where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let validated = if refresh_from_node { update_wallet_state( wallet_inst.clone(), keychain_mask, status_send_channel, false, )? } else { false }; wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); let txs = updater::retrieve_txs( &mut **w, tx_id, tx_slate_id, query_args, Some(&parent_key_id), false, )?; Ok((validated, txs)) } /// Retrieve summary info pub fn retrieve_summary_info<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, refresh_from_node: bool, minimum_confirmations: u64, ) -> Result<(bool, WalletInfo), Error> where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let validated = if refresh_from_node { update_wallet_state( wallet_inst.clone(), keychain_mask, status_send_channel, false, )? } else { false }; wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); let wallet_info = updater::retrieve_info(&mut **w, &parent_key_id, minimum_confirmations)?; Ok((validated, wallet_info)) } /// Retrieve payment proof pub fn retrieve_payment_proof<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { if tx_id.is_none() && tx_slate_id.is_none() { return Err(Error::PaymentProofRetrieval( "Transaction ID or Slate UUID must be specified".to_owned(), )); } if refresh_from_node { update_wallet_state( wallet_inst.clone(), keychain_mask, status_send_channel, false, )? } else { false }; let txs = retrieve_txs( wallet_inst.clone(), keychain_mask, status_send_channel, refresh_from_node, tx_id, tx_slate_id, None, )?; if txs.1.len() != 1 { return Err(Error::PaymentProofRetrieval( "Transaction doesn't exist".to_owned(), )); } // Pull out all needed fields, returning an error if they're not present let tx = txs.1[0].clone(); let proof = match tx.payment_proof { Some(p) => p, None => { return Err(Error::PaymentProofRetrieval( "Transaction does not contain a payment proof".to_owned(), )); } }; let amount = if tx.amount_credited >= tx.amount_debited { tx.amount_credited - tx.amount_debited } else { let fee = match tx.fee { Some(f) => f.fee(), // apply fee mask past HF4 None => 0, }; tx.amount_debited - tx.amount_credited - fee }; let excess = match tx.kernel_excess { Some(e) => e, None => { return Err(Error::PaymentProofRetrieval( "Transaction does not contain kernel excess".to_owned(), )); } }; let r_sig = match proof.receiver_signature { Some(e) => e, None => { return Err(Error::PaymentProofRetrieval( "Proof does not contain receiver signature ".to_owned(), )); } }; let s_sig = match proof.sender_signature { Some(e) => e, None => { return Err(Error::PaymentProofRetrieval( "Proof does not contain sender signature ".to_owned(), )); } }; Ok(PaymentProof { amount: amount, excess: excess, recipient_address: SlatepackAddress::new(&proof.receiver_address), recipient_sig: r_sig, sender_address: SlatepackAddress::new(&proof.sender_address), sender_sig: s_sig, }) } /// Retrieve invoice payment proof /// TODO: Need to unify with legacy above pub fn retrieve_payment_proof_invoice<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, refresh_from_node: bool, tx_id: Option, tx_slate_id: Option, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { if tx_id.is_none() && tx_slate_id.is_none() { return Err(Error::PaymentProofRetrieval( "Transaction ID or Slate UUID must be specified".to_owned(), )); } if refresh_from_node { update_wallet_state( wallet_inst.clone(), keychain_mask, status_send_channel, false, )? } else { false }; let txs = retrieve_txs( wallet_inst.clone(), keychain_mask, status_send_channel, refresh_from_node, tx_id, tx_slate_id, None, )?; if txs.1.len() != 1 { return Err(Error::PaymentProofRetrieval( "Transaction doesn't exist".to_owned(), )); } // Pull out all needed fields, returning an error if they're not present let tx = txs.1[0].clone(); let amount = if tx.amount_credited >= tx.amount_debited { tx.amount_credited - tx.amount_debited } else { // TODO: Invoice proof not expecting fee included here tx.amount_debited - tx.amount_credited }; let (mut proof, sender_part_sig) = match tx.payment_proof { Some(p) => { if p.receiver_public_nonce.is_none() { return Err(Error::PaymentProofRetrieval( "Invoice Proof requires stored receiver public nonce".into(), )); }; if p.receiver_public_excess.is_none() { return Err(Error::PaymentProofRetrieval( "Invoice Proof requires stored receiver public excess".into(), )); }; if p.timestamp.is_none() { return Err(Error::PaymentProofRetrieval( "Invoice Proof requires stored timestamp".into(), )); }; if p.sender_part_sig.is_none() { return Err(Error::PaymentProofRetrieval( "Invoice Proof requires stored sender partial signature".into(), )); }; ( InvoiceProof { proof_type: if let Some(t) = p.proof_type { t } else { 1u8 }, amount, receiver_public_nonce: p.receiver_public_nonce.unwrap(), receiver_public_excess: p.receiver_public_excess.unwrap(), sender_address: p.sender_address, timestamp: p.timestamp.unwrap().timestamp(), memo: p.memo, promise_signature: p.promise_signature, witness_data: None, }, p.sender_part_sig.unwrap(), ) } None => { return Err(Error::PaymentProofRetrieval( "Transaction does not contain a payment proof".to_owned(), )); } }; // Now to kernel lookup, to fill in the witness data // Check kernel exists let mut client = { wallet_lock!(wallet_inst, w); w.w2n_client().clone() }; let kernel_excess = match tx.kernel_excess { Some(k) => k, None => { return Err(Error::PaymentProofRetrieval(format!( "Invoice proof transaction kernel excess missing", ))) } }; let (retrieved_kernel, index) = match client.get_kernel(&kernel_excess, None, None) { Err(e) => { return Err(Error::PaymentProof(format!( "Error retrieving kernel from chain: {}", e ))); } Ok(None) => { return Err(Error::PaymentProof(format!( "Transaction kernel with excess {:?} not found on chain", kernel_excess ))); } Ok(Some((k, _, index))) => (k, index), }; proof.witness_data = Some(ProofWitness { kernel_index: index, kernel_commitment: retrieved_kernel.excess, sender_partial_sig: sender_part_sig, }); Ok(proof) } /// Initiate tx as sender pub fn init_send_tx<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, args: InitTxArgs, use_test_rng: bool, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let parent_key_id = match &args.src_acct_name { Some(d) => { let pm = w.get_acct_path(d.clone())?; match pm { Some(p) => p.path, None => w.parent_key_id(), } } None => w.parent_key_id(), }; let mut slate = tx::new_tx_slate( &mut *w, args.amount, false, 2, use_test_rng, args.ttl_blocks, )?; if let Some(v) = args.target_slate_version { slate.version_info.version = v; }; // if we just want to estimate, don't save a context, just send the results // back if let Some(true) = args.estimate_only { let (total, fee) = tx::estimate_send_tx( &mut *w, keychain_mask, args.amount, args.amount_includes_fee.unwrap_or(false), args.minimum_confirmations, args.max_outputs as usize, args.num_change_outputs as usize, args.selection_strategy_is_use_all, &parent_key_id, )?; slate.amount = total; slate.fee_fields = fee.try_into().unwrap(); return Ok(slate); } let height = w.w2n_client().get_chain_tip()?.0; let mut context = if args.late_lock.unwrap_or(false) { tx::create_late_lock_context( &mut *w, keychain_mask, &mut slate, height, &args, &parent_key_id, use_test_rng, )? } else { tx::add_inputs_to_slate( &mut *w, keychain_mask, &mut slate, height, args.minimum_confirmations, args.max_outputs as usize, args.num_change_outputs as usize, args.selection_strategy_is_use_all, &parent_key_id, true, use_test_rng, args.amount_includes_fee.unwrap_or(false), )? }; // Payment Proof, add addresses to slate and save address // TODO: Note we only use single derivation path for now, // probably want to allow sender to specify which one let deriv_path = 0u32; if let Some(a) = args.payment_proof_recipient_address { let k = w.keychain(keychain_mask)?; let sec_addr_key = address::address_from_derivation_path(&k, &parent_key_id, deriv_path)?; let sender_address = OnionV3Address::from_private(&sec_addr_key.0)?; slate.payment_proof = Some(PaymentInfo { sender_address: Some(sender_address.to_ed25519()?), receiver_address: a.pub_key, promise_signature: None, timestamp: DateTime::::from_utc( NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), Utc, ), memo: None, }); context.payment_proof_derivation_index = Some(deriv_path); } // Save the aggsig context in our DB for when we // recieve the transaction back { let mut batch = w.batch(keychain_mask)?; batch.save_private_context(slate.id.as_bytes(), &context)?; batch.commit()?; } slate.compact()?; Ok(slate) } /// Initiate a transaction as the recipient (invoicing) pub fn issue_invoice_tx<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, args: IssueInvoiceTxArgs, use_test_rng: bool, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let parent_key_id = match args.dest_acct_name { Some(d) => { let pm = w.get_acct_path(d)?; match pm { Some(p) => p.path, None => w.parent_key_id(), } } None => w.parent_key_id(), }; let mut slate = tx::new_tx_slate(&mut *w, args.amount, true, 2, use_test_rng, None)?; let height = w.w2n_client().get_chain_tip()?.0; let context = tx::add_output_to_slate( &mut *w, keychain_mask, &mut slate, height, &parent_key_id, true, use_test_rng, )?; if let Some(v) = args.target_slate_version { slate.version_info.version = v; }; // Save the aggsig context in our DB for when we // recieve the transaction back { let mut batch = w.batch(keychain_mask)?; batch.save_private_context(slate.id.as_bytes(), &context)?; batch.commit()?; } slate.compact()?; Ok(slate) } /// Receive an invoice tx, essentially adding inputs to whatever /// output was specified pub fn process_invoice_tx<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, slate: &Slate, args: InitTxArgs, use_test_rng: bool, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let mut ret_slate = slate.clone(); check_ttl(w, &ret_slate)?; let parent_key_id = match args.src_acct_name { Some(d) => { let pm = w.get_acct_path(d)?; match pm { Some(p) => p.path, None => w.parent_key_id(), } } None => w.parent_key_id(), }; // Don't do this multiple times let tx = updater::retrieve_txs( &mut *w, None, Some(ret_slate.id), None, Some(&parent_key_id), use_test_rng, )?; for t in &tx { if t.tx_type == TxLogEntryType::TxSent { return Err(Error::TransactionAlreadyReceived(ret_slate.id.to_string())); } if t.tx_type == TxLogEntryType::TxSentCancelled { return Err(Error::TransactionWasCancelled(ret_slate.id.to_string())); } } let height = w.w2n_client().get_chain_tip()?.0; // update ttl if desired if let Some(b) = args.ttl_blocks { ret_slate.ttl_cutoff_height = height + b; } // if this is compact mode, we need to create the transaction now ret_slate.tx = Some(Slate::empty_transaction()); // if self sending, make sure to store 'initiator' keys let context_res = w.get_private_context(keychain_mask, slate.id.as_bytes()); let mut context = tx::add_inputs_to_slate( &mut *w, keychain_mask, &mut ret_slate, height, args.minimum_confirmations, args.max_outputs as usize, args.num_change_outputs as usize, args.selection_strategy_is_use_all, &parent_key_id, false, use_test_rng, false, )?; let keychain = w.keychain(keychain_mask)?; // Add our contribution to the offset if context_res.is_ok() { // Self sending: don't correct for inputs and outputs // here, as we will do it during finalization. let mut tmp_context = context.clone(); tmp_context.input_ids.clear(); tmp_context.output_ids.clear(); ret_slate.adjust_offset(&keychain, &tmp_context)?; } else { ret_slate.adjust_offset(&keychain, &context)?; } // needs to be stored as we're removing sig data for return trip. this needs to be present // when locking transaction context and updating tx log with excess later context.calculated_excess = Some(ret_slate.calc_excess(keychain.secp())?); // if self-sending, merge contexts if let Ok(c) = context_res { context.initial_sec_key = c.initial_sec_key; context.initial_sec_nonce = c.initial_sec_nonce; context.fee = c.fee; context.amount = c.amount; for o in c.output_ids.iter() { context.output_ids.push(o.clone()); } for i in c.input_ids.iter() { context.input_ids.push(i.clone()); } } selection::repopulate_tx(&mut *w, keychain_mask, &mut ret_slate, &context, false)?; // Save the aggsig context in our DB for when we // recieve the transaction back { let mut batch = w.batch(keychain_mask)?; batch.save_private_context(slate.id.as_bytes(), &context)?; batch.commit()?; } // Can remove amount as well as other sig data now ret_slate.amount = 0; ret_slate.remove_other_sigdata(&keychain, &context.sec_nonce, &context.sec_key)?; ret_slate.state = SlateState::Invoice2; Ok(ret_slate) } /// Lock sender outputs pub fn tx_lock_outputs<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, slate: &Slate, ) -> Result<(), Error> where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; let mut excess_override = None; let mut sl = slate.clone(); if sl.tx == None { sl.tx = Some(Slate::empty_transaction()); selection::repopulate_tx(&mut *w, keychain_mask, &mut sl, &context, true)?; } if slate.participant_data.len() == 1 { // purely for invoice workflow, payer needs the excess back temporarily for storage excess_override = context.calculated_excess; } let height = w.w2n_client().get_chain_tip()?.0; selection::lock_tx_context( &mut *w, keychain_mask, &sl, height, &context, excess_override, ) } /// Finalize slate pub fn finalize_tx<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, slate: &Slate, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { foreign_finalize(w, keychain_mask, slate, false) } /// cancel tx pub fn cancel_tx<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, tx_id: Option, tx_slate_id: Option, ) -> Result<(), Error> where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { if !update_wallet_state( wallet_inst.clone(), keychain_mask, status_send_channel, false, )? { return Err(Error::TransactionCancellationError( "Can't contact running Grin node. Not Cancelling.", )); } wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); tx::cancel_tx(&mut **w, keychain_mask, &parent_key_id, tx_id, tx_slate_id) } /// get stored tx /// crashes if stored tx has total fees exceeding 2^40 nanogrin pub fn get_stored_tx<'a, T: ?Sized, C, K>( w: &T, tx_id: Option, slate_id: Option<&Uuid>, ) -> Result, Error> where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let mut uuid = None; if let Some(i) = tx_id { let tx = w.tx_log_iter().find(|t| t.id == i); if let Some(t) = tx { uuid = t.tx_slate_id; } } if uuid.is_none() { if let Some(sid) = slate_id { uuid = Some(sid.to_owned()); } } let id = match uuid { Some(u) => u, None => { return Err(Error::StoredTx( "Both the provided Transaction Id and Slate UUID are invalid.".to_owned(), )); } }; let tx_res = w.get_stored_tx(&format!("{}", id))?; match tx_res { Some(tx) => { let mut slate = Slate::blank(2, false); slate.tx = Some(tx.clone()); slate.fee_fields = tx.aggregate_fee_fields().unwrap(); // apply fee mask past HF4 slate.id = id; slate.offset = tx.offset; slate.state = SlateState::Standard3; Ok(Some(slate)) } None => Ok(None), } } /// Posts a transaction to the chain /// take a client impl instead of wallet so as not to have to lock the wallet pub fn post_tx<'a, C>(client: &C, tx: &Transaction, fluff: bool) -> Result<(), Error> where C: NodeClient + 'a, { let res = client.post_tx(tx, fluff); if let Err(e) = res { error!("api: post_tx: failed with error: {}", e); Err(e) } else { debug!( "api: post_tx: successfully posted tx: {}, fluff? {}", tx.hash(), fluff ); Ok(()) } } /// Scan outputs with the rewind hash of a third-party wallet. /// Help to retrieve outputs information that belongs it pub fn scan_rewind_hash<'a, L, C, K>( wallet_inst: Arc>>>, rewind_hash: String, start_height: Option, status_send_channel: &Option>, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let is_hex = rewind_hash.chars().all(|c| c.is_ascii_hexdigit()); let rewind_hash = rewind_hash.to_lowercase(); if !(is_hex && rewind_hash.len() == 64) { let msg = format!("Invalid Rewind Hash"); return Err(Error::RewindHash(msg)); } let tip = { wallet_lock!(wallet_inst, w); w.w2n_client().get_chain_tip()? }; let start_height = match start_height { Some(h) => h, None => 1, }; let info = scan::scan_rewind_hash( wallet_inst, rewind_hash, start_height, tip.0, status_send_channel, )?; Ok(info) } /// check repair /// Accepts a wallet inst instead of a raw wallet so it can /// lock as little as possible pub fn scan<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, start_height: Option, delete_unconfirmed: bool, status_send_channel: &Option>, ) -> Result<(), Error> where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { update_outputs(wallet_inst.clone(), keychain_mask, true)?; let tip = { wallet_lock!(wallet_inst, w); w.w2n_client().get_chain_tip()? }; let start_height = match start_height { Some(h) => h, None => 1, }; let mut info = scan::scan( wallet_inst.clone(), keychain_mask, delete_unconfirmed, start_height, tip.0, status_send_channel, )?; info.hash = tip.1; wallet_lock!(wallet_inst, w); let mut batch = w.batch(keychain_mask)?; batch.save_last_scanned_block(info)?; batch.commit()?; Ok(()) } /// node height pub fn node_height<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let res = { wallet_lock!(wallet_inst, w); w.w2n_client().get_chain_tip() }; match res { Ok(r) => Ok(NodeHeightResult { height: r.0, header_hash: r.1, updated_from_node: true, }), Err(_) => { let outputs = retrieve_outputs(wallet_inst, keychain_mask, &None, true, false, None)?; let height = match outputs.1.iter().map(|m| m.output.height).max() { Some(height) => height, None => 0, }; Ok(NodeHeightResult { height, header_hash: "".to_owned(), updated_from_node: false, }) } } } /// Experimental, wrap the entire definition of how a wallet's state is updated pub fn update_wallet_state<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, status_send_channel: &Option>, update_all: bool, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let parent_key_id = { wallet_lock!(wallet_inst, w); w.parent_key_id() }; let client = { wallet_lock!(wallet_inst, w); w.w2n_client().clone() }; // Step 1: Update outputs and transactions purely based on UTXO state if let Some(ref s) = status_send_channel { let _ = s.send(StatusMessage::UpdatingOutputs( "Updating outputs from node".to_owned(), )); } let mut result = update_outputs(wallet_inst.clone(), keychain_mask, update_all)?; if !result { if let Some(ref s) = status_send_channel { let _ = s.send(StatusMessage::UpdateWarning( "Updater Thread unable to contact node".to_owned(), )); } return Ok(result); } if let Some(ref s) = status_send_channel { let _ = s.send(StatusMessage::UpdatingTransactions( "Updating transactions".to_owned(), )); } // Step 2: Update outstanding transactions with no change outputs by kernel let mut txs = { wallet_lock!(wallet_inst, w); updater::retrieve_txs(&mut **w, None, None, None, Some(&parent_key_id), true)? }; result = update_txs_via_kernel(wallet_inst.clone(), keychain_mask, &mut txs)?; if !result { if let Some(ref s) = status_send_channel { let _ = s.send(StatusMessage::UpdateWarning( "Updater Thread unable to contact node".to_owned(), )); } return Ok(result); } // Step 3: Scan back a bit on the chain let res = client.get_chain_tip(); // if we can't get the tip, don't continue let tip = match res { Ok(t) => t, Err(_) => { if let Some(ref s) = status_send_channel { let _ = s.send(StatusMessage::UpdateWarning( "Updater Thread unable to contact node".to_owned(), )); } return Ok(false); } }; // Check if this is a restored wallet that needs a full scan let last_scanned_block = { wallet_lock!(wallet_inst, w); match w.init_status()? { WalletInitStatus::InitNeedsScanning => ScannedBlockInfo { height: 0, hash: "".to_owned(), start_pmmr_index: 0, last_pmmr_index: 0, }, WalletInitStatus::InitNoScanning => ScannedBlockInfo { height: tip.clone().0, hash: tip.clone().1, start_pmmr_index: 0, last_pmmr_index: 0, }, WalletInitStatus::InitComplete => w.last_scanned_block()?, } }; let start_index = last_scanned_block.height.saturating_sub(100); if last_scanned_block.height == 0 { let msg = "This wallet has not been scanned against the current chain. Beginning full scan... (this first scan may take a while, but subsequent scans will be much quicker)".to_string(); if let Some(ref s) = status_send_channel { let _ = s.send(StatusMessage::FullScanWarn(msg)); } } let mut info = scan::scan( wallet_inst.clone(), keychain_mask, false, start_index, tip.0, status_send_channel, )?; info.hash = tip.1; { wallet_lock!(wallet_inst, w); let mut batch = w.batch(keychain_mask)?; batch.save_last_scanned_block(info)?; // init considered complete after first successful update batch.save_init_status(WalletInitStatus::InitComplete)?; batch.commit()?; } // Step 5: Cancel any transactions with an expired TTL for tx in txs { if let Some(e) = tx.ttl_cutoff_height { if tip.0 >= e { wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); tx::cancel_tx(&mut **w, keychain_mask, &parent_key_id, Some(tx.id), None)?; } } } Ok(result) } /// Check TTL pub fn check_ttl<'a, T: ?Sized, C, K>(w: &mut T, slate: &Slate) -> Result<(), Error> where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { // Refuse if TTL is expired let last_confirmed_height = w.last_confirmed_height()?; if slate.ttl_cutoff_height != 0 { if last_confirmed_height >= slate.ttl_cutoff_height { return Err(Error::TransactionExpired); } } Ok(()) } /// Verify/validate arbitrary payment proof /// Returns (whether this wallet is the sender, whether this wallet is the recipient) pub fn verify_payment_proof<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, proof: &PaymentProof, ) -> Result<(bool, bool), Error> where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let sender_pubkey = proof.sender_address.pub_key; let msg = tx::payment_proof_message(proof.amount, &proof.excess, sender_pubkey)?; let (mut client, parent_key_id, keychain) = { wallet_lock!(wallet_inst, w); ( w.w2n_client().clone(), w.parent_key_id(), w.keychain(keychain_mask)?, ) }; // Check kernel exists match client.get_kernel(&proof.excess, None, None) { Err(e) => { return Err(Error::PaymentProof(format!( "Error retrieving kernel from chain: {}", e ))); } Ok(None) => { return Err(Error::PaymentProof(format!( "Transaction kernel with excess {:?} not found on chain", proof.excess ))); } Ok(Some(_)) => {} }; // Check Sigs let recipient_pubkey = proof.recipient_address.pub_key; if recipient_pubkey.verify(&msg, &proof.recipient_sig).is_err() { return Err(Error::PaymentProof( "Invalid recipient signature".to_owned(), )); }; let sender_pubkey = proof.sender_address.pub_key; if sender_pubkey.verify(&msg, &proof.sender_sig).is_err() { return Err(Error::PaymentProof("Invalid sender signature".to_owned())); }; // for now, simple test as to whether one of the addresses belongs to this wallet let sec_key = address::address_from_derivation_path(&keychain, &parent_key_id, 0)?; let d_skey = match DalekSecretKey::from_bytes(&sec_key.0) { Ok(k) => k, Err(e) => { return Err(Error::ED25519Key(format!("{}", e))); } }; let my_address_pubkey: DalekPublicKey = (&d_skey).into(); let sender_mine = my_address_pubkey == sender_pubkey; let recipient_mine = my_address_pubkey == recipient_pubkey; Ok((sender_mine, recipient_mine)) } /// Attempt to update outputs in wallet, return whether it was successful fn update_outputs<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, update_all: bool, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { wallet_lock!(wallet_inst, w); let parent_key_id = w.parent_key_id(); match updater::refresh_outputs(&mut **w, keychain_mask, &parent_key_id, update_all) { Ok(_) => Ok(true), Err(e) => { if let Error::InvalidKeychainMask = e { return Err(e); } Ok(false) } } } /// Update transactions that need to be validated via kernel lookup fn update_txs_via_kernel<'a, L, C, K>( wallet_inst: Arc>>>, keychain_mask: Option<&SecretKey>, txs: &mut Vec, ) -> Result where L: WalletLCProvider<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let parent_key_id = { wallet_lock!(wallet_inst, w); w.parent_key_id() }; let mut client = { wallet_lock!(wallet_inst, w); w.w2n_client().clone() }; let height = match client.get_chain_tip() { Ok(h) => h.0, Err(_) => return Ok(false), }; for tx in txs.iter_mut() { if tx.confirmed { continue; } if tx.amount_debited != 0 && tx.amount_credited != 0 { continue; } if let Some(e) = tx.kernel_excess { let res = client.get_kernel(&e, tx.kernel_lookup_min_height, Some(height)); let kernel = match res { Ok(k) => k, Err(_) => return Ok(false), }; if let Some(k) = kernel { debug!("Kernel Retrieved: {:?}", k); wallet_lock!(wallet_inst, w); let mut batch = w.batch(keychain_mask)?; tx.confirmed = true; tx.update_confirmation_ts(); batch.save_tx_log_entry(tx.clone(), &parent_key_id)?; batch.commit()?; } } else { warn!("Attempted to update via kernel excess for transaction {:?}, but kernel excess was not stored", tx.tx_slate_id); } } Ok(true) } /// Builds an output for the wallet's next available key pub fn build_output<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, features: OutputFeatures, amount: u64, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let k = w.keychain(keychain_mask)?; let key_id = keys::next_available_key(&mut *w, keychain_mask)?; let blind = k.derive_key(amount, &key_id, SwitchCommitmentType::Regular)?; let commit = k.secp().commit(amount, blind.clone())?; let proof_builder = proof::ProofBuilder::new(&k); let proof = proof::create( &k, &proof_builder, amount, &key_id, SwitchCommitmentType::Regular, commit, None, )?; let output = Output::new(features, commit, proof); Ok(BuiltOutput { blind: BlindingFactor::from_secret_key(blind), key_id: key_id, output: output, }) } // Contract implementation /// Initialize transaction contract pub fn contract_new<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, args: &ContractNewArgsAPI, // use_test_rng: bool, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { contract::new(&mut *w, keychain_mask, &args.setup_args) } // /// Setup transaction contract // pub fn contract_setup<'a, T: ?Sized, C, K>( // w: &mut T, // keychain_mask: Option<&SecretKey>, // args: &ContractSetupArgsAPI, // slate: &Slate, // // use_test_rng: bool, // ) -> Result // where // T: WalletBackend<'a, C, K>, // C: NodeClient + 'a, // K: Keychain + 'a, // { // contract::setup(&mut *w, keychain_mask, slate, &args) // } /// Sign transaction contract pub fn contract_sign<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, args: &ContractSetupArgsAPI, slate: &Slate, // use_test_rng: bool, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { contract::sign(&mut *w, keychain_mask, slate, &args) } /// Revoke transaction contract pub fn contract_revoke<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, args: &ContractRevokeArgsAPI, // use_test_rng: bool, ) -> Result, Error> where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { contract::revoke(&mut *w, keychain_mask, &args) } /// Revoke transaction contract pub fn get_slate_index_matching_my_context<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, slate: &Slate, // use_test_rng: bool, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let keychain = w.keychain(keychain_mask)?; let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; slate.find_index_matching_context(&keychain, &context) } /// Create MXMixnet request pub fn create_mwixnet_req<'a, T: ?Sized, C, K>( w: &mut T, keychain_mask: Option<&SecretKey>, params: &MixnetReqCreationParams, slate: &Slate, // use_test_rng: bool, ) -> Result where T: WalletBackend<'a, C, K>, C: NodeClient + 'a, K: Keychain + 'a, { let context = w.get_private_context(keychain_mask, slate.id.as_bytes())?; let my_keys = context.get_private_keys(); let kernel = slate.tx_or_err()?.kernels()[0]; let msg = kernel.msg_to_sign()?; let comsig = ComSignature::sign(slate.amount, &my_keys.0, &msg.to_hex().as_bytes().to_vec())?; let mut hops: Vec = Vec::new(); let mut final_commit = kernel.excess.clone(); let mut final_blind = my_keys.0.clone(); for i in 0..params.server_keys.len() { let excess = params.server_keys[i].clone(); let secp = secp256k1zkp::Secp256k1::with_caps(secp256k1zkp::ContextFlag::Commit); final_blind.add_assign(&secp, &excess).unwrap(); final_commit = add_excess(&final_commit, &excess).unwrap(); let proof = if i == params.server_keys.len() - 1 { let n1 = random_secret(); let rp = secp.bullet_proof( slate.amount - (params.fee_per_hop * params.server_keys.len() as u32) as u64, final_blind.clone(), n1.clone(), n1.clone(), None, None, ); assert!(secp.verify_bullet_proof(final_commit, rp, None).is_ok()); Some(rp) } else { None }; let hop = new_hop(¶ms.server_keys[i], &excess, params.fee_per_hop, proof); hops.push(hop); } let onion = create_onion(&kernel.excess, &hops)?; Ok(SwapReq { comsig, onion }) //slate.find_index_matching_context(&keychain, &context) }