diff --git a/src/crypto.rs b/src/crypto.rs index ac4d4ee..df17729 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,12 +1,11 @@ use std::collections::HashMap; use anyhow::{Error, Result}; +use serde::{Deserialize, Serialize}; use sp_client::{ bitcoin::{ - consensus::serde::hex, - hex::DisplayHex, key::constants::SECRET_KEY_SIZE, - secp256k1::{ecdh::SharedSecret, PublicKey, SecretKey}, + secp256k1::{ecdh::SharedSecret, PublicKey}, Txid, }, silentpayments::{ @@ -14,9 +13,7 @@ use sp_client::{ sending::SilentPaymentAddress, }, }; - -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use tsify::Tsify; use aes_gcm::{ aead::{Aead, AeadInPlace, Nonce}, @@ -36,38 +33,45 @@ const HALFKEYSIZE: usize = SECRET_KEY_SIZE / 2; const THIRTYTWO: usize = 32; -type SharedPublicKey = PublicKey; - #[derive(Debug)] -pub struct AnkSharedSecret(SharedSecret); +pub struct SharedPoint([u8; 64]); + +impl SharedPoint { + pub fn as_inner(&self) -> &[u8; 64] { + &self.0 + } +} + +#[derive(Debug, Serialize, Deserialize, Tsify, Clone)] +#[tsify(from_wasm_abi, into_wasm_abi)] +pub struct AnkSharedSecret { + secret: [u8; 32], + pub trusted: bool, +} impl AnkSharedSecret { - pub fn new_from_public_key(public_key: SharedPublicKey) -> Self { - let t_hash = SharedPublicKeyHash::from_shared_pubkey(public_key); - Self(SharedSecret::from_bytes(t_hash.to_byte_array())) + pub fn new(shared_point: [u8; 64], trusted: bool) -> Self { + let secret = AnkSharedSecretHash::from_shared_point(shared_point).to_byte_array(); + Self { secret, trusted } } - pub fn to_byte_array(&self) -> [u8; SECRET_KEY_SIZE] { - self.0.secret_bytes() - } - - pub fn to_string(&self) -> String { - format!("{}", self.0.display_secret()) + pub fn to_byte_array(&self) -> [u8; 32] { + self.secret } } sha256t_hash_newtype! { - pub struct SharedPublicKeyTag = hash_str("4nk/SharedPublicKey"); + pub struct AnkSharedSecretTag = hash_str("4nk/AnkSharedSecret"); #[hash_newtype(forward)] - pub struct SharedPublicKeyHash(_); + pub struct AnkSharedSecretHash(_); } -impl SharedPublicKeyHash { - pub fn from_shared_pubkey(shared_pubkey: SharedPublicKey) -> Self { - let mut eng = SharedPublicKeyHash::engine(); - eng.input(&shared_pubkey.serialize()); - SharedPublicKeyHash::from_engine(eng) +impl AnkSharedSecretHash { + pub fn from_shared_point(shared_point: [u8; 64]) -> Self { + let mut eng = AnkSharedSecretHash::engine(); + eng.input(&shared_point); + AnkSharedSecretHash::from_engine(eng) } } @@ -214,7 +218,7 @@ pub struct Aes256Encryption { plaintext: Vec, aes_key: [u8; 32], nonce: [u8; 12], - shared_secrets: HashMap>, + shared_secrets: HashMap>, } impl Aes256Encryption { @@ -227,7 +231,7 @@ impl Aes256Encryption { pub fn set_shared_secret( &mut self, - shared_secrets: HashMap>, + shared_secrets: HashMap>, ) { self.shared_secrets = shared_secrets; } @@ -240,7 +244,7 @@ impl Aes256Encryption { for (_, sp_address2shared_secret) in self.shared_secrets.iter() { for (sp_address, shared_secret) in sp_address2shared_secret { - let cipher = Aes256Gcm::new_from_slice(shared_secret.as_ref()) + let cipher = Aes256Gcm::new_from_slice(&shared_secret.secret) .map_err(|e| Error::msg(format!("{}", e)))?; let nonce = Aes256Gcm::generate_nonce(&mut rng); let encrypted_key = cipher @@ -327,6 +331,8 @@ impl Aes256Encryption { mod tests { use std::str::FromStr; + use sp_client::bitcoin::hex::FromHex; + use super::*; const ALICE_SP_ADDRESS: &str = "tsp1qqw3lqr6xravz9nf8ntazgwwl0fqv47kfjdxsnxs6eutavqfwyv5q6qk97mmyf6dtkdyzqlu2zv6h9j2ggclk7vn705q5u2phglpq7yw3dg5rwpdz"; @@ -399,11 +405,14 @@ mod tests { let mut aes_enc = Aes256Encryption::new(Purpose::Login, plaintext.to_vec()).unwrap(); let mut shared_secrets: HashMap = HashMap::new(); - let mut sp_address2shared_secrets: HashMap = + let mut sp_address2shared_secrets: HashMap = HashMap::new(); + let alice_secret = Vec::from_hex(ALICE_SHARED_SECRET).unwrap(); + let mut buf = [0u8;32]; + buf.copy_from_slice(&alice_secret); sp_address2shared_secrets.insert( ALICE_SP_ADDRESS.try_into().unwrap(), - SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(), + AnkSharedSecret { secret: buf, trusted: true } ); shared_secrets.insert( Txid::from_str(TRANSACTION).unwrap(), @@ -448,15 +457,21 @@ mod tests { Aes256Encryption::new(Purpose::ThirtyTwoBytes, plaintext.to_vec()).unwrap(); let mut shared_secrets: HashMap = HashMap::new(); - let mut sp_address2shared_secrets: HashMap = + let mut sp_address2shared_secrets: HashMap = HashMap::new(); + let alice_secret = Vec::from_hex(ALICE_SHARED_SECRET).unwrap(); + let mut buf = [0u8;32]; + buf.copy_from_slice(&alice_secret); sp_address2shared_secrets.insert( ALICE_SP_ADDRESS.try_into().unwrap(), - SharedSecret::from_str(ALICE_SHARED_SECRET).unwrap(), + AnkSharedSecret { secret: buf, trusted: true } ); + let bob_secret = Vec::from_hex(BOB_SHARED_SECRET).unwrap(); + let mut buf = [0u8;32]; + buf.copy_from_slice(&bob_secret); sp_address2shared_secrets.insert( BOB_SP_ADDRESS.try_into().unwrap(), - SharedSecret::from_str(BOB_SHARED_SECRET).unwrap(), + AnkSharedSecret { secret: buf, trusted: true } ); shared_secrets.insert( Txid::from_str(TRANSACTION).unwrap(), diff --git a/src/lib.rs b/src/lib.rs index a688c6e..48fa577 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod crypto; pub mod network; +pub mod silentpayments; diff --git a/src/network.rs b/src/network.rs index dea07f8..c4204e6 100644 --- a/src/network.rs +++ b/src/network.rs @@ -138,17 +138,19 @@ impl Envelope { pub enum AnkFlag { NewTx, Faucet, + Prd, Error, Unknown, } impl From<&str> for AnkFlag { - fn from(value: &str) -> Self { + fn from(value: &str) -> Self { match value { "NewTx" => Self::NewTx, "Faucet" => Self::Faucet, + "Prd" => Self::Prd, "Error" => Self::Error, - _ => Self::Unknown + _ => Self::Unknown, } } } @@ -164,8 +166,9 @@ impl AnkFlag { match byte { 0 => Self::NewTx, 1 => Self::Faucet, - 2 => Self::Error, - _ => Self::Unknown + 2 => Self::Prd, + 9 => Self::Error, + _ => Self::Unknown, } } @@ -173,8 +176,9 @@ impl AnkFlag { match self { Self::NewTx => "new_tx", Self::Faucet => "faucet", + Self::Prd => "prd", Self::Error => "error", - Self::Unknown => "unknown" + Self::Unknown => "unknown", } } } @@ -187,9 +191,7 @@ pub struct FaucetMessage { impl FaucetMessage { pub fn new(sp_address: String) -> Self { - Self { - sp_address - } + Self { sp_address } } } @@ -197,14 +199,14 @@ impl FaucetMessage { #[tsify(into_wasm_abi, from_wasm_abi)] pub struct NewTxMessage { pub transaction: String, - pub tweak_data: Option + pub tweak_data: Option, } impl NewTxMessage { pub fn new(transaction: String, tweak_data: Option) -> Self { - Self { - transaction, - tweak_data + Self { + transaction, + tweak_data, } } } @@ -220,17 +222,17 @@ pub struct AnkNetworkMsg { // type Error = anyhow::Error; // fn try_from(value: &str) -> std::prelude::v1::Result { // let parsed: Value = serde_json::from_str(value)?; -// let flag = parsed +// let flag = parsed // .get("flag") // .ok_or(Error::msg("Invalid AnkNetworkMsg"))? // .as_str() // .ok_or(Error::msg("Invalid AnkNetworkMsg"))?; -// let content = parsed +// let content = parsed // .get("content") // .ok_or(Error::msg("Invalid AnkNetworkMsg"))? // .as_str() // .ok_or(Error::msg("Invalid AnkNetworkMsg"))?; -// Ok(Self { flag: flag.into(), content: content.into() }) +// Ok(Self { flag: flag.into(), content: content.into() }) // } // } @@ -238,7 +240,7 @@ impl AnkNetworkMsg { pub fn new(flag: AnkFlag, raw: &str) -> Self { Self { flag, - content: raw.into() + content: raw.into(), } } } diff --git a/src/silentpayments.rs b/src/silentpayments.rs new file mode 100644 index 0000000..51c6c34 --- /dev/null +++ b/src/silentpayments.rs @@ -0,0 +1,113 @@ +use std::collections::HashMap; + +use anyhow::{Error, Result}; + +use rand::Rng; +use serde::{Deserialize, Serialize}; +use sp_client::bitcoin::secp256k1::ecdh::shared_secret_point; +use sp_client::bitcoin::{secp256k1::PublicKey, Transaction}; +use sp_client::bitcoin::{Amount, OutPoint, Txid}; +use sp_client::silentpayments::sending::SilentPaymentAddress; +use sp_client::spclient::{OwnedOutput, Recipient, SpClient, SpWallet}; +use tsify::Tsify; + +use crate::crypto::AnkSharedSecret; + +// #[derive(Debug, Serialize, Deserialize, Tsify)] +// #[tsify(into_wasm_abi, from_wasm_abi)] +// pub struct ScannedTransaction(HashMap>); + +// impl ScannedTransaction { +// pub fn new() -> Self { +// Self(HashMap::new()) +// } + +// pub fn get(&self) -> &HashMap> { +// &self.0 +// } + +// pub fn get_mut(&mut self) -> &mut HashMap> { +// &mut self.0 +// } + +// pub fn to_inner(&self) -> HashMap> { +// self.0.clone() +// } +// } + +pub fn create_transaction_for_address_with_shared_secret( + sp_address: SilentPaymentAddress, + sp_wallet: &SpWallet, + message: String, + fee_rate: Amount, +) -> Result<(Transaction, AnkSharedSecret)> { + let available_outpoints = sp_wallet.get_outputs().to_spendable_list(); + + // Here we need to add more heuristics about which outpoint we spend + // For now let's keep it simple + + let mut inputs: HashMap = HashMap::new(); + + let mut total_available = Amount::from_sat(0); + for (outpoint, output) in available_outpoints { + total_available += output.amount; + inputs.insert(outpoint, output); + if total_available > Amount::from_sat(1000) { + break; + } + } + + if total_available < Amount::from_sat(1000) { + return Err(Error::msg("Not enough available funds")); + } + + let recipient = Recipient { + address: sp_address.into(), + amount: Amount::from_sat(1000), + nb_outputs: 1, + }; + + let mut new_psbt = sp_wallet.get_client().create_new_psbt( + inputs, + vec![recipient], + Some(message.as_bytes()), + )?; + + let change_addr = sp_wallet.get_client().sp_receiver.get_change_address(); + SpClient::set_fees(&mut new_psbt, fee_rate, change_addr)?; + + let partial_secret = sp_wallet + .get_client() + .get_partial_secret_from_psbt(&new_psbt)?; + + // This wouldn't work with many recipients in the same transaction + // each address (or more precisely each scan public key) would have its own point + let shared_point = shared_secret_point(&sp_address.get_scan_key(), &partial_secret); + + sp_wallet + .get_client() + .fill_sp_outputs(&mut new_psbt, partial_secret)?; + let mut aux_rand = [0u8; 32]; + rand::thread_rng().fill(&mut aux_rand); + let mut signed = sp_wallet.get_client().sign_psbt(new_psbt, &aux_rand)?; + SpClient::finalize_psbt(&mut signed)?; + + let final_tx = signed.extract_tx()?; + + Ok((final_tx, AnkSharedSecret::new(shared_point, true))) +} + +// This need to go +pub fn check_transaction( + tx: &Transaction, + sp_wallet: &mut SpWallet, + blockheight: u32, + tweak_data: PublicKey, +) -> Result { + let txid = tx.txid().to_string(); + if sp_wallet.update_wallet_with_transaction(tx, blockheight, tweak_data)? > 0 { + return Ok(txid); + } + + return Err(Error::msg("No new outputs found")); +}