diff --git a/src/lib.rs b/src/lib.rs index cea88fa..025b6bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(non_snake_case, dead_code)] +#![allow(non_snake_case)] pub mod receiving; pub mod sending; pub mod structs; diff --git a/src/receiving.rs b/src/receiving.rs index 75334d2..ec9ff3d 100644 --- a/src/receiving.rs +++ b/src/receiving.rs @@ -5,40 +5,31 @@ use std::{collections::HashMap, str::FromStr}; use crate::{ structs::{OutputWithSignature, ScannedOutput}, - utils::ser_uint32, + utils::{ser_uint32, Result}, }; pub fn get_receiving_addresses( B_scan: PublicKey, B_spend: PublicKey, labels: &HashMap, -) -> Vec { +) -> Result> { let mut receiving_addresses: Vec = vec![]; - receiving_addresses.push(encode_silent_payment_address(B_scan, B_spend, None, None)); + receiving_addresses.push(encode_silent_payment_address(B_scan, B_spend, None, None)?); for (_, label) in labels { receiving_addresses.push(create_labeled_silent_payment_address( B_scan, B_spend, label, None, None, - )); + )?); } - receiving_addresses + Ok(receiving_addresses) } -pub fn get_A_sum_public_keys(input: &Vec) -> PublicKey { - let keys: Vec = input - .iter() - .map(|x| match PublicKey::from_str(&x) { - Ok(key) => key, - Err(_) => { - // we always assume even pairing for input public keys if they are omitted - let x_only_public_key = XOnlyPublicKey::from_str(&x).unwrap(); - PublicKey::from_x_only_public_key(x_only_public_key, secp256k1::Parity::Even) - } - }) - .collect(); - let keys_refs: Vec<&PublicKey> = keys.iter().collect(); +pub fn get_A_sum_public_keys( + input: &Vec, +) -> std::result::Result { + let keys_refs: &Vec<&PublicKey> = &input.iter().collect(); - PublicKey::combine_keys(&keys_refs).unwrap() + PublicKey::combine_keys(keys_refs) } fn encode_silent_payment_address( @@ -46,9 +37,9 @@ fn encode_silent_payment_address( B_m: PublicKey, hrp: Option<&str>, version: Option, -) -> String { +) -> Result { let hrp = hrp.unwrap_or("sp"); - let version = bech32::u5::try_from_u8(version.unwrap_or(0)).unwrap(); + let version = bech32::u5::try_from_u8(version.unwrap_or(0))?; let B_scan_bytes = B_scan.serialize(); let B_m_bytes = B_m.serialize(); @@ -57,7 +48,7 @@ fn encode_silent_payment_address( data.insert(0, version); - bech32::encode(hrp, data, bech32::Variant::Bech32m).unwrap() + Ok(bech32::encode(hrp, data, bech32::Variant::Bech32m)?) } fn create_labeled_silent_payment_address( @@ -66,32 +57,26 @@ fn create_labeled_silent_payment_address( m: &String, hrp: Option<&str>, version: Option, -) -> String { - let bytes = hex::decode(m).unwrap().try_into().unwrap(); +) -> Result { + let bytes: [u8; 32] = hex::decode(m)?.as_slice().try_into()?; - let scalar = Scalar::from_be_bytes(bytes).unwrap(); + let scalar = Scalar::from_be_bytes(bytes)?; let secp = Secp256k1::new(); - let G: PublicKey = SecretKey::from_slice(&Scalar::ONE.to_be_bytes()) - .unwrap() - .public_key(&secp); - let intermediate = G.mul_tweak(&secp, &scalar).unwrap(); - let B_m = intermediate.combine(&B_spend).unwrap(); + let G: PublicKey = SecretKey::from_slice(&Scalar::ONE.to_be_bytes())?.public_key(&secp); + let intermediate = G.mul_tweak(&secp, &scalar)?; + let B_m = intermediate.combine(&B_spend)?; encode_silent_payment_address(B_scan, B_m, hrp, version) } -fn calculate_P_n(B_spend: &PublicKey, t_n: [u8; 32]) -> PublicKey { +fn calculate_P_n(B_spend: &PublicKey, t_n: [u8; 32]) -> Result { let secp = Secp256k1::new(); - let G: PublicKey = SecretKey::from_slice(&Scalar::ONE.to_be_bytes()) - .unwrap() - .public_key(&secp); - let intermediate = G - .mul_tweak(&secp, &Scalar::from_be_bytes(t_n).unwrap()) - .unwrap(); - let P_n = intermediate.combine(&B_spend).unwrap(); + let G: PublicKey = SecretKey::from_slice(&Scalar::ONE.to_be_bytes())?.public_key(&secp); + let intermediate = G.mul_tweak(&secp, &Scalar::from_be_bytes(t_n)?)?; + let P_n = intermediate.combine(&B_spend)?; - P_n + Ok(P_n) } fn calculate_t_n(ecdh_shared_secret: &[u8; 33], n: u32) -> [u8; 32] { @@ -105,14 +90,14 @@ fn calculate_ecdh_secret( A_sum: &PublicKey, b_scan: SecretKey, outpoints_hash: [u8; 32], -) -> [u8; 33] { +) -> Result<[u8; 33]> { let secp = Secp256k1::new(); - let intermediate = A_sum.mul_tweak(&secp, &b_scan.into()).unwrap(); - let scalar = Scalar::from_be_bytes(outpoints_hash).unwrap(); - let ecdh_shared_secret = intermediate.mul_tweak(&secp, &scalar).unwrap().serialize(); + let intermediate = A_sum.mul_tweak(&secp, &b_scan.into())?; + let scalar = Scalar::from_be_bytes(outpoints_hash)?; + let ecdh_shared_secret = intermediate.mul_tweak(&secp, &scalar)?.serialize(); - ecdh_shared_secret + Ok(ecdh_shared_secret) } pub fn scanning( @@ -122,9 +107,9 @@ pub fn scanning( outpoints_hash: [u8; 32], outputs_to_check: Vec, labels: Option<&HashMap>, -) -> Vec { +) -> Result> { let secp = secp256k1::Secp256k1::new(); - let ecdh_shared_secret = calculate_ecdh_secret(&A_sum, b_scan, outpoints_hash); + let ecdh_shared_secret = calculate_ecdh_secret(&A_sum, b_scan, outpoints_hash)?; let mut n = 0; let mut wallet: Vec = vec![]; @@ -132,7 +117,7 @@ pub fn scanning( while found { found = false; let t_n = calculate_t_n(&ecdh_shared_secret, n); - let P_n = calculate_P_n(&B_spend, t_n); + let P_n = calculate_P_n(&B_spend, t_n)?; let (P_n_xonly, _) = P_n.x_only_public_key(); if outputs_to_check.iter().any(|&output| output.eq(&P_n_xonly)) { let pub_key = hex::encode(P_n_xonly.serialize()); @@ -149,23 +134,19 @@ pub fn scanning( let output_even = output.public_key(secp256k1::Parity::Even); let output_odd = output.public_key(secp256k1::Parity::Odd); - let m_G_sub_even = output_even.combine(&P_n_negated).unwrap(); - let m_G_sub_odd = output_odd.combine(&P_n_negated).unwrap(); + let m_G_sub_even = output_even.combine(&P_n_negated)?; + let m_G_sub_odd = output_odd.combine(&P_n_negated)?; let keys: Vec = vec![m_G_sub_even, m_G_sub_odd]; for labelkeystr in labels.keys() { - let labelkey = PublicKey::from_str(labelkeystr).unwrap(); + let labelkey = PublicKey::from_str(labelkeystr)?; if keys.iter().any(|x| x.eq(&labelkey)) { let P_nm = hex::encode(output.serialize()); let label = labels.get(labelkeystr).unwrap(); - let label_bytes = hex::decode(label).unwrap().try_into().unwrap(); - let label_scalar = Scalar::from_be_bytes(label_bytes).unwrap(); - let t_n_as_secret_key = SecretKey::from_slice(&t_n).unwrap(); - let priv_key_tweak = hex::encode( - t_n_as_secret_key - .add_tweak(&label_scalar) - .unwrap() - .secret_bytes(), - ); + let label_bytes = hex::decode(label)?.as_slice().try_into()?; + let label_scalar = Scalar::from_be_bytes(label_bytes)?; + let t_n_as_secret_key = SecretKey::from_slice(&t_n)?; + let priv_key_tweak = + hex::encode(t_n_as_secret_key.add_tweak(&label_scalar)?.secret_bytes()); wallet.push(ScannedOutput { pub_key: P_nm, priv_key_tweak, @@ -177,23 +158,23 @@ pub fn scanning( } } } - wallet + Ok(wallet) } pub fn verify_and_calculate_signatures( add_to_wallet: &mut Vec, b_spend: SecretKey, -) -> Result, secp256k1::Error> { +) -> Result> { let secp = secp256k1::Secp256k1::new(); let msg = Message::from_hashed_data::(b"message"); let aux = secp256k1::hashes::sha256::Hash::hash(b"random auxiliary data").to_byte_array(); let mut res: Vec = vec![]; for output in add_to_wallet { - let pubkey = XOnlyPublicKey::from_str(&output.pub_key).unwrap(); - let tweak = hex::decode(&output.priv_key_tweak).unwrap(); - let scalar = Scalar::from_be_bytes(tweak.try_into().unwrap()).unwrap(); - let mut full_priv_key = b_spend.add_tweak(&scalar).unwrap(); + let pubkey = XOnlyPublicKey::from_str(&output.pub_key)?; + let tweak: [u8; 32] = hex::decode(&output.priv_key_tweak)?.as_slice().try_into()?; + let scalar = Scalar::from_be_bytes(tweak)?; + let mut full_priv_key = b_spend.add_tweak(&scalar)?; let (_, parity) = full_priv_key.x_only_public_key(&secp); diff --git a/src/sending.rs b/src/sending.rs index 3de649a..5a1ebf7 100644 --- a/src/sending.rs +++ b/src/sending.rs @@ -1,66 +1,64 @@ use bech32::FromBase32; use secp256k1::{Parity, PublicKey, Scalar, Secp256k1, SecretKey}; -use std::{collections::HashMap, str::FromStr}; +use std::collections::HashMap; -use crate::utils::{hash_outpoints, ser_uint32, sha256}; +use crate::utils::{hash_outpoints, ser_uint32, sha256, Result}; -fn get_a_sum_secret_keys(input: &Vec<(String, bool)>) -> SecretKey { +fn get_a_sum_secret_keys(input: &Vec<(SecretKey, bool)>) -> Result { let secp = Secp256k1::new(); let mut negated_keys: Vec = vec![]; - for (keystr, is_xonly) in input { - let key = SecretKey::from_str(&keystr).unwrap(); + for (key, x_only) in input { let (_, parity) = key.x_only_public_key(&secp); - if *is_xonly && parity == Parity::Odd { + if *x_only && parity == Parity::Odd { negated_keys.push(key.negate()); } else { - negated_keys.push(key); + negated_keys.push(*key); } } - let (head, tail) = negated_keys.split_first().unwrap(); + let (head, tail) = negated_keys.split_first().ok_or("Empty input list")?; - let result: SecretKey = tail + let result: Result = tail .iter() - .fold(*head, |acc, &item| acc.add_tweak(&item.into()).unwrap()); + .fold(Ok(*head), |acc: Result, &item| { + Ok(acc?.add_tweak(&item.into())?) + }); result } -fn decode_silent_payment_address(addr: &str) -> (PublicKey, PublicKey) { - let (_hrp, data, _variant) = bech32::decode(&addr).unwrap(); +fn decode_silent_payment_address(addr: &str) -> Result<(PublicKey, PublicKey)> { + let (_hrp, data, _variant) = bech32::decode(&addr)?; - let data = Vec::::from_base32(&data[1..]).unwrap(); + let data = Vec::::from_base32(&data[1..])?; - let B_scan = PublicKey::from_slice(&data[..33]).unwrap(); - let B_spend = PublicKey::from_slice(&data[33..]).unwrap(); + let B_scan = PublicKey::from_slice(&data[..33])?; + let B_spend = PublicKey::from_slice(&data[33..])?; - (B_scan, B_spend) + Ok((B_scan, B_spend)) } pub fn create_outputs( outpoints: &Vec<(String, u32)>, - input_priv_keys: &Vec<(String, bool)>, + input_priv_keys: &Vec<(SecretKey, bool)>, recipients: &Vec<(String, f32)>, -) -> Vec> { +) -> Result>> { let secp = Secp256k1::new(); - let outpoints_hash = hash_outpoints(outpoints); + let outpoints_hash = hash_outpoints(outpoints)?; - let a_sum = get_a_sum_secret_keys(input_priv_keys); + let a_sum = get_a_sum_secret_keys(input_priv_keys)?; let mut silent_payment_groups: HashMap> = HashMap::new(); for (payment_address, amount) in recipients { - let (B_scan, B_m) = decode_silent_payment_address(&payment_address); + let (B_scan, B_m) = decode_silent_payment_address(&payment_address)?; - if silent_payment_groups.contains_key(&B_scan) { - silent_payment_groups - .get_mut(&B_scan) - .unwrap() - .push((B_m, *amount)); + if let Some(payments) = silent_payment_groups.get_mut(&B_scan) { + payments.push((B_m, *amount)); } else { silent_payment_groups.insert(B_scan, vec![(B_m, *amount)]); } @@ -71,9 +69,9 @@ pub fn create_outputs( let mut n = 0; //calculate shared secret - let intermediate = B_scan.mul_tweak(&secp, &a_sum.into()).unwrap(); - let scalar = Scalar::from_be_bytes(outpoints_hash).unwrap(); - let ecdh_shared_secret = intermediate.mul_tweak(&secp, &scalar).unwrap().serialize(); + let intermediate = B_scan.mul_tweak(&secp, &a_sum.into())?; + let scalar = Scalar::from_be_bytes(outpoints_hash)?; + let ecdh_shared_secret = intermediate.mul_tweak(&secp, &scalar)?.serialize(); for (B_m, amount) in B_m_values { let mut bytes: Vec = Vec::new(); @@ -81,15 +79,10 @@ pub fn create_outputs( bytes.extend_from_slice(&ser_uint32(n)); let t_n = sha256(&bytes); - // eprintln!("t_n = {:?}", hex::encode(t_n)); - let G: PublicKey = SecretKey::from_slice(&Scalar::ONE.to_be_bytes()) - .unwrap() - .public_key(&secp); - let res = G - .mul_tweak(&secp, &Scalar::from_be_bytes(t_n).unwrap()) - .unwrap(); - let reskey = res.combine(&B_m).unwrap(); + let G: PublicKey = SecretKey::from_slice(&Scalar::ONE.to_be_bytes())?.public_key(&secp); + let res = G.mul_tweak(&secp, &Scalar::from_be_bytes(t_n)?)?; + let reskey = res.combine(&B_m)?; let (reskey_xonly, _) = reskey.x_only_public_key(); let mut toAdd: HashMap = HashMap::new(); @@ -100,5 +93,5 @@ pub fn create_outputs( n += 1; } } - result + Ok(result) } diff --git a/src/utils.rs b/src/utils.rs index a872c90..87bd2c6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,8 @@ use std::io::Write; use hex::FromHex; use secp256k1::hashes::{sha256, Hash}; +pub type Result = std::result::Result>; + pub fn sha256(message: &[u8]) -> [u8; 32] { sha256::Hash::hash(message).to_byte_array() } @@ -11,11 +13,11 @@ pub fn ser_uint32(u: u32) -> Vec { u.to_be_bytes().into() } -pub fn hash_outpoints(sending_data: &Vec<(String, u32)>) -> [u8; 32] { +pub fn hash_outpoints(sending_data: &Vec<(String, u32)>) -> Result<[u8; 32]> { let mut outpoints: Vec> = vec![]; for (txid_str, vout) in sending_data { - let mut txid = Vec::from_hex(txid_str).unwrap(); + let mut txid = Vec::from_hex(txid_str)?; txid.reverse(); let mut vout_bytes = vout.to_le_bytes().to_vec(); txid.append(&mut vout_bytes); @@ -26,8 +28,8 @@ pub fn hash_outpoints(sending_data: &Vec<(String, u32)>) -> [u8; 32] { let mut engine = sha256::HashEngine::default(); for v in outpoints { - engine.write_all(&v).unwrap(); + engine.write_all(&v)?; } - sha256::Hash::from_engine(engine).to_byte_array() + Ok(sha256::Hash::from_engine(engine).to_byte_array()) } diff --git a/tests/vector_tests.rs b/tests/vector_tests.rs index ad27d27..5935557 100644 --- a/tests/vector_tests.rs +++ b/tests/vector_tests.rs @@ -9,7 +9,7 @@ use silentpayments::utils; mod tests { use std::{collections::HashSet, str::FromStr}; - use secp256k1::XOnlyPublicKey; + use secp256k1::{PublicKey, SecretKey, XOnlyPublicKey}; use crate::{ common::input::{self, get_testing_silent_payment_key_pair, ComparableHashMap, TestData}, @@ -40,8 +40,14 @@ mod tests { let expected_comparable: HashSet = expected.outputs.into_iter().map(|x| x.into()).collect(); + let input_priv_keys: Vec<(SecretKey, bool)> = given + .input_priv_keys + .iter() + .map(|(keystr, x_only)| (SecretKey::from_str(&keystr).unwrap(), *x_only)) + .collect(); + let outputs = - create_outputs(&given.outpoints, &given.input_priv_keys, &given.recipients); + create_outputs(&given.outpoints, &input_priv_keys, &given.recipients).unwrap(); for map in &outputs { for key in map.keys() { @@ -68,7 +74,8 @@ mod tests { let (b_scan, b_spend, B_scan, B_spend) = get_testing_silent_payment_key_pair(&given.bip32_seed); - let receiving_addresses = get_receiving_addresses(B_scan, B_spend, &given.labels); + let receiving_addresses = + get_receiving_addresses(B_scan, B_spend, &given.labels).unwrap(); let set1: HashSet<_> = receiving_addresses.iter().collect(); let set2: HashSet<_> = expected.addresses.iter().collect(); @@ -82,8 +89,26 @@ mod tests { .map(|x| XOnlyPublicKey::from_str(x).unwrap()) .collect(); - let outpoints_hash = hash_outpoints(&given.outpoints); - let A_sum = get_A_sum_public_keys(&given.input_pub_keys); + let outpoints_hash = hash_outpoints(&given.outpoints).unwrap(); + + let input_pub_keys: Vec = given + .input_pub_keys + .iter() + .map(|x| match PublicKey::from_str(&x) { + Ok(key) => key, + Err(_) => { + // we always assume even pairing for input public keys if they are omitted + let x_only_public_key = XOnlyPublicKey::from_str(&x).unwrap(); + PublicKey::from_x_only_public_key( + x_only_public_key, + secp256k1::Parity::Even, + ) + } + }) + .collect(); + + let A_sum = get_A_sum_public_keys(&input_pub_keys).unwrap(); + let labels = match &given.labels.len() { 0 => None, _ => Some(&given.labels), @@ -96,7 +121,8 @@ mod tests { outpoints_hash, outputs_to_check, labels, - ); + ) + .unwrap(); let res = verify_and_calculate_signatures(&mut add_to_wallet, b_spend).unwrap(); assert_eq!(res, expected.outputs);