diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6ed370b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,40 @@ +#[derive(Debug)] +pub enum Error { + GenericError(String), + InvalidLabel(String), + InvalidAddress(String), + InvalidSharedSecret(String), + Secp256k1Error(secp256k1::Error), + OutOfRangeError(secp256k1::scalar::OutOfRangeError), + IOError(std::io::Error), +} + +impl From for Error { + fn from(e: hex::FromHexError) -> Self { + Error::InvalidLabel(e.to_string()) + } +} + +impl From for Error { + fn from(e: bech32::Error) -> Self { + Error::InvalidAddress(e.to_string()) + } +} + +impl From for Error { + fn from(e: secp256k1::Error) -> Self { + Error::Secp256k1Error(e) + } +} + +impl From for Error { + fn from(e: secp256k1::scalar::OutOfRangeError) -> Self { + Error::OutOfRangeError(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IOError(e) + } +} diff --git a/src/lib.rs b/src/lib.rs index 025b6bf..89efc58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![allow(non_snake_case)] +mod error; pub mod receiving; pub mod sending; pub mod structs; -pub mod utils; +mod utils; diff --git a/src/receiving.rs b/src/receiving.rs index ec9ff3d..b0147f1 100644 --- a/src/receiving.rs +++ b/src/receiving.rs @@ -1,11 +1,15 @@ use bech32::ToBase32; use secp256k1::{hashes::Hash, Message, PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey}; -use std::{collections::HashMap, str::FromStr}; +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; use crate::{ - structs::{OutputWithSignature, ScannedOutput}, - utils::{ser_uint32, Result}, + error::Error, + structs::{Outpoint, OutputWithSignature, ScannedOutput}, + utils::{hash_outpoints, ser_uint32, Result}, }; pub fn get_receiving_addresses( @@ -58,7 +62,10 @@ fn create_labeled_silent_payment_address( hrp: Option<&str>, version: Option, ) -> Result { - let bytes: [u8; 32] = hex::decode(m)?.as_slice().try_into()?; + let bytes: [u8; 32] = hex::decode(m)? + .as_slice() + .try_into() + .map_err(|_| Error::GenericError("Wrong byte length".to_owned()))?; let scalar = Scalar::from_be_bytes(bytes)?; let secp = Secp256k1::new(); @@ -104,11 +111,14 @@ pub fn scanning( b_scan: SecretKey, B_spend: PublicKey, A_sum: PublicKey, - outpoints_hash: [u8; 32], + outpoints: HashSet, outputs_to_check: Vec, labels: Option<&HashMap>, ) -> Result> { let secp = secp256k1::Secp256k1::new(); + + let outpoints_hash = hash_outpoints(&outpoints)?; + let ecdh_shared_secret = calculate_ecdh_secret(&A_sum, b_scan, outpoints_hash)?; let mut n = 0; let mut wallet: Vec = vec![]; @@ -142,7 +152,10 @@ pub fn scanning( 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)?.as_slice().try_into()?; + let label_bytes = hex::decode(label)? + .as_slice() + .try_into() + .map_err(|_| Error::GenericError("Wrong byte length".to_owned()))?; let label_scalar = Scalar::from_be_bytes(label_bytes)?; let t_n_as_secret_key = SecretKey::from_slice(&t_n)?; let priv_key_tweak = @@ -172,7 +185,10 @@ pub fn verify_and_calculate_signatures( let mut res: Vec = vec![]; for output in add_to_wallet { let pubkey = XOnlyPublicKey::from_str(&output.pub_key)?; - let tweak: [u8; 32] = hex::decode(&output.priv_key_tweak)?.as_slice().try_into()?; + let tweak: [u8; 32] = hex::decode(&output.priv_key_tweak)? + .as_slice() + .try_into() + .map_err(|_| Error::GenericError("Wrong byte length".to_owned()))?; let scalar = Scalar::from_be_bytes(tweak)?; let mut full_priv_key = b_spend.add_tweak(&scalar)?; diff --git a/src/sending.rs b/src/sending.rs index 5a1ebf7..683ec96 100644 --- a/src/sending.rs +++ b/src/sending.rs @@ -1,97 +1,172 @@ -use bech32::FromBase32; +use bech32::{FromBase32, ToBase32}; -use secp256k1::{Parity, PublicKey, Scalar, Secp256k1, SecretKey}; +use secp256k1::{PublicKey, Secp256k1, SecretKey, XOnlyPublicKey}; use std::collections::HashMap; -use crate::utils::{hash_outpoints, ser_uint32, sha256, Result}; +use crate::{ + error::Error, + utils::{ser_uint32, sha256, Result}, +}; -fn get_a_sum_secret_keys(input: &Vec<(SecretKey, bool)>) -> Result { +struct SilentPaymentAddress { + version: u8, + scan_pubkey: PublicKey, + m_pubkey: PublicKey, + is_testnet: bool, +} + +impl SilentPaymentAddress { + pub fn new( + scan_pubkey: PublicKey, + m_pubkey: PublicKey, + is_testnet: bool, + version: u8, + ) -> Result { + if version != 0 { + return Err(Error::GenericError( + "Can't have other version than 0 for now".to_owned(), + )); + } + + Ok(SilentPaymentAddress { + scan_pubkey, + m_pubkey, + is_testnet, + version, + }) + } +} + +impl TryFrom<&str> for SilentPaymentAddress { + type Error = Error; + + fn try_from(addr: &str) -> Result { + let (hrp, data, _variant) = bech32::decode(&addr)?; + + if data.len() != 107 { + return Err(Error::GenericError("Address length is wrong".to_owned())); + } + + let version = data[0].to_u8(); + + let is_testnet = match hrp.as_str() { + "sp" => false, + "tsp" => true, + _ => { + return Err(Error::InvalidAddress(format!( + "Wrong prefix, expected \"sp\" or \"tsp\", got \"{}\"", + &hrp + ))) + } + }; + + let data = Vec::::from_base32(&data[1..])?; + + let scan_pubkey = PublicKey::from_slice(&data[..33])?; + let m_pubkey = PublicKey::from_slice(&data[33..])?; + + SilentPaymentAddress::new(scan_pubkey, m_pubkey, is_testnet, version.into()) + } +} + +impl TryFrom for SilentPaymentAddress { + type Error = Error; + + fn try_from(addr: String) -> Result { + addr.as_str().try_into() + } +} + +impl Into for SilentPaymentAddress { + fn into(self) -> String { + let hrp = match self.is_testnet { + true => "tsp", + false => "sp", + }; + + let version = bech32::u5::try_from_u8(self.version).unwrap(); + + let B_scan_bytes = self.scan_pubkey.serialize(); + let B_m_bytes = self.m_pubkey.serialize(); + + let mut data = [B_scan_bytes, B_m_bytes].concat().to_base32(); + + data.insert(0, version); + + bech32::encode(hrp, data, bech32::Variant::Bech32m).unwrap() + } +} + +/// Create outputs for a given set of silent payment recipients and their corresponding shared secrets. +/// +/// # Arguments +/// +/// * `recipients` - A `Vec` of silent payment addresses to be paid. +/// * `ecdh_shared_secrets` - A HashMap that maps every scan key to a shared secret created with this scan key. +/// +/// # Returns +/// +/// If successful, the function returns a `Result` wrapping a `HashMap` of silent payment addresses to a `Vec`. +/// The `Vec` contains all the outputs that are associated with the silent payment address. +/// +/// # Errors +/// +/// This function will return an error if: +/// +/// * The recipients Vec contains a silent payment address with an incorrect format. +/// * The ecdh_shared_secrets does not contain a secret for every B_scan that are being paid to. +/// * Edge cases are hit during elliptic curve computation (extremely unlikely). +pub fn generate_recipient_pubkeys( + recipients: Vec, + ecdh_shared_secrets: HashMap, +) -> Result>> { let secp = Secp256k1::new(); - let mut negated_keys: Vec = vec![]; + let mut silent_payment_groups: HashMap)> = + HashMap::new(); + for recipient in recipients { + let recipient: SilentPaymentAddress = recipient.try_into()?; + let B_scan = recipient.scan_pubkey; - for (key, x_only) in input { - let (_, parity) = key.x_only_public_key(&secp); - - if *x_only && parity == Parity::Odd { - negated_keys.push(key.negate()); + if let Some((_, payments)) = silent_payment_groups.get_mut(&B_scan) { + payments.push(recipient); } else { - negated_keys.push(*key); + let ecdh_shared_secret = ecdh_shared_secrets + .get(&B_scan) + .ok_or(Error::InvalidSharedSecret( + "Shared secret for this B_scan not found".to_owned(), + ))? + .to_owned(); + silent_payment_groups.insert(B_scan, (ecdh_shared_secret, vec![recipient])); } } - let (head, tail) = negated_keys.split_first().ok_or("Empty input list")?; - - let result: Result = tail - .iter() - .fold(Ok(*head), |acc: Result, &item| { - Ok(acc?.add_tweak(&item.into())?) - }); - - result -} - -fn decode_silent_payment_address(addr: &str) -> Result<(PublicKey, PublicKey)> { - let (_hrp, data, _variant) = bech32::decode(&addr)?; - - let data = Vec::::from_base32(&data[1..])?; - - let B_scan = PublicKey::from_slice(&data[..33])?; - let B_spend = PublicKey::from_slice(&data[33..])?; - - Ok((B_scan, B_spend)) -} - -pub fn create_outputs( - outpoints: &Vec<(String, u32)>, - input_priv_keys: &Vec<(SecretKey, bool)>, - recipients: &Vec<(String, f32)>, -) -> Result>> { - let secp = Secp256k1::new(); - - let outpoints_hash = hash_outpoints(outpoints)?; - - 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)?; - - 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)]); - } - } - - let mut result: Vec> = vec![]; - for (B_scan, B_m_values) in silent_payment_groups.into_iter() { + let mut result: HashMap> = HashMap::new(); + for group in silent_payment_groups.into_values() { let mut n = 0; - //calculate shared secret - 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(); + let (ecdh_shared_secret, recipients) = group; - for (B_m, amount) in B_m_values { + for recipient in recipients { let mut bytes: Vec = Vec::new(); - bytes.extend_from_slice(&ecdh_shared_secret); + bytes.extend_from_slice(&ecdh_shared_secret.serialize()); bytes.extend_from_slice(&ser_uint32(n)); let t_n = sha256(&bytes); - 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 res = SecretKey::from_slice(&t_n)?.public_key(&secp); + let reskey = res.combine(&recipient.m_pubkey)?; let (reskey_xonly, _) = reskey.x_only_public_key(); - let mut toAdd: HashMap = HashMap::new(); - - toAdd.insert(reskey_xonly.to_string(), amount); - - result.push(toAdd); + let entry = result.entry(recipient.into()).or_insert_with(Vec::new); + entry.push(reskey_xonly); n += 1; } } Ok(result) } + +pub fn decode_scan_pubkey(silent_payment_address: String) -> Result { + let address: SilentPaymentAddress = silent_payment_address.try_into()?; + Ok(address.scan_pubkey) +} diff --git a/src/structs.rs b/src/structs.rs index 6bcf806..255ae3e 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -12,3 +12,9 @@ pub struct OutputWithSignature { pub priv_key_tweak: String, pub signature: String, } + +#[derive(PartialEq, Eq, Hash)] +pub struct Outpoint { + pub txid: [u8; 32], + pub vout: u32, +} diff --git a/src/utils.rs b/src/utils.rs index 87bd2c6..d799593 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,10 @@ -use std::io::Write; +use std::{collections::HashSet, io::Write}; -use hex::FromHex; use secp256k1::hashes::{sha256, Hash}; -pub type Result = std::result::Result>; +use crate::{error::Error, structs::Outpoint}; + +pub type Result = std::result::Result; pub fn sha256(message: &[u8]) -> [u8; 32] { sha256::Hash::hash(message).to_byte_array() @@ -13,22 +14,25 @@ pub fn ser_uint32(u: u32) -> Vec { u.to_be_bytes().into() } -pub fn hash_outpoints(sending_data: &Vec<(String, u32)>) -> Result<[u8; 32]> { +pub fn hash_outpoints(sending_data: &HashSet) -> Result<[u8; 32]> { let mut outpoints: Vec> = vec![]; - for (txid_str, vout) in sending_data { - 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); - outpoints.push(txid); + for outpoint in sending_data { + let txid = outpoint.txid; + let vout = outpoint.vout; + + let mut bytes: Vec = Vec::new(); + bytes.extend_from_slice(&txid); + bytes.reverse(); + bytes.extend_from_slice(&vout.to_le_bytes()); + outpoints.push(bytes); } outpoints.sort(); let mut engine = sha256::HashEngine::default(); for v in outpoints { - engine.write_all(&v)?; + engine.write_all(&v).unwrap(); } Ok(sha256::Hash::from_engine(engine).to_byte_array()) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 7839bc5..4f0446a 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1 +1,2 @@ -pub mod input; +pub mod structs; +pub mod utils; diff --git a/tests/common/input.rs b/tests/common/structs.rs similarity index 50% rename from tests/common/input.rs rename to tests/common/structs.rs index 254ef59..a9267d1 100644 --- a/tests/common/input.rs +++ b/tests/common/structs.rs @@ -1,10 +1,8 @@ #![allow(non_snake_case)] use serde::Deserialize; -use serde_json::from_str; use silentpayments::structs::OutputWithSignature; -use std::hash::{Hash, Hasher}; -use std::{collections::HashMap, fs::File, io::Read}; +use std::collections::HashMap; #[derive(Debug, Deserialize)] pub struct TestData { @@ -54,48 +52,3 @@ pub struct SendingDataGiven { pub struct SendingDataExpected { pub outputs: Vec<(String, f32)>, } - -#[derive(Debug)] -pub struct ComparableHashMap { - pub inner: HashMap, -} - -impl From> for ComparableHashMap { - fn from(map: HashMap) -> Self { - ComparableHashMap { inner: map } - } -} - -impl PartialEq for ComparableHashMap { - fn eq(&self, other: &Self) -> bool { - if self.inner.len() != other.inner.len() { - return false; - } - - self.inner.iter().all(|(key, val)| { - other - .inner - .get(key) - .map_or(false, |other_val| (val - other_val).abs() < 0.0001) - }) - } -} - -impl Eq for ComparableHashMap {} - -impl Hash for ComparableHashMap { - fn hash(&self, state: &mut H) { - let mut keys: Vec<_> = self.inner.keys().collect(); - keys.sort(); // ensure consistent order - for key in keys { - key.hash(state); - } - } -} - -pub fn read_file() -> Vec { - let mut file = File::open("tests/resources/send_and_receive_test_vectors.json").unwrap(); - let mut contents = String::new(); - file.read_to_string(&mut contents).unwrap(); - from_str(&contents).unwrap() -} diff --git a/tests/common/utils.rs b/tests/common/utils.rs new file mode 100644 index 0000000..6b86510 --- /dev/null +++ b/tests/common/utils.rs @@ -0,0 +1,118 @@ +use std::{collections::HashSet, fs::File, io::{Read, Write}, str::FromStr}; + +use secp256k1::{PublicKey, SecretKey, XOnlyPublicKey, hashes::{sha256, Hash}, Scalar}; +use serde_json::from_str; +use silentpayments::structs::Outpoint; + +use super::structs::TestData; + +pub fn read_file() -> Vec { + let mut file = File::open("tests/resources/send_and_receive_test_vectors.json").unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + from_str(&contents).unwrap() +} + +pub fn decode_outpoints(outpoints: &Vec<(String, u32)>) -> HashSet { + outpoints + .iter() + .map(|(txid_str, vout)| Outpoint { + txid: hex::decode(txid_str) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + vout: *vout, + }) + .collect() +} + +pub fn decode_priv_keys(input_priv_keys: &Vec<(String, bool)>) -> Vec<(SecretKey, bool)> { + input_priv_keys + .iter() + .map(|(keystr, x_only)| (SecretKey::from_str(&keystr).unwrap(), *x_only)) + .collect() +} + +pub fn decode_input_pub_keys(input_pub_keys: &Vec) -> Vec { + 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() +} + +pub fn decode_outputs_to_check(outputs: &Vec) -> Vec { + outputs + .iter() + .map(|x| XOnlyPublicKey::from_str(x).unwrap()) + .collect() +} + +pub fn decode_recipients(recipients: &Vec<(String, f32)>) -> Vec { + recipients + .iter() + .map(|(sp_addr_str, _)| sp_addr_str.to_owned()) + .collect() +} + +pub fn get_a_sum_secret_keys(input: &Vec<(SecretKey, bool)>) -> SecretKey { + let secp = secp256k1::Secp256k1::new(); + + let mut negated_keys: Vec = vec![]; + + for (key, is_xonly) in input { + let (_, parity) = key.x_only_public_key(&secp); + + if *is_xonly && parity == secp256k1::Parity::Odd { + negated_keys.push(key.negate()); + } else { + negated_keys.push(key.clone()); + } + } + + let (head, tail) = negated_keys.split_first().unwrap(); + + let result: SecretKey = tail + .iter() + .fold(*head, |acc, &item| acc.add_tweak(&item.into()).unwrap()); + + result +} + +pub fn compute_ecdh_shared_secret(a_sum: SecretKey, B_scan: PublicKey, outpoints_hash: Scalar) -> PublicKey { + let secp = secp256k1::Secp256k1::new(); + + let diffie_hellman = B_scan.mul_tweak(&secp, &a_sum.into()).unwrap(); + diffie_hellman.mul_tweak(&secp, &outpoints_hash).unwrap() +} + +pub fn hash_outpoints(sending_data: &HashSet) -> [u8; 32] { + let mut outpoints: Vec> = vec![]; + + for outpoint in sending_data { + let txid = outpoint.txid; + let vout = outpoint.vout; + + let mut bytes: Vec = Vec::new(); + bytes.extend_from_slice(&txid); + bytes.reverse(); + bytes.extend_from_slice(&vout.to_le_bytes()); + outpoints.push(bytes); + } + outpoints.sort(); + + let mut engine = sha256::HashEngine::default(); + + for v in outpoints { + engine.write_all(&v).unwrap(); + } + + sha256::Hash::from_engine(engine).to_byte_array() +} diff --git a/tests/vector_tests.rs b/tests/vector_tests.rs index e8c417d..bf2e56b 100644 --- a/tests/vector_tests.rs +++ b/tests/vector_tests.rs @@ -2,28 +2,32 @@ mod common; use silentpayments::receiving; -use silentpayments::sending; -use silentpayments::utils; #[cfg(test)] mod tests { - use std::{collections::HashSet, str::FromStr}; + use std::{collections::{HashSet, HashMap}, str::FromStr}; - use secp256k1::{PublicKey, SecretKey, XOnlyPublicKey}; + use secp256k1::{SecretKey, PublicKey, Scalar}; + use silentpayments::sending::{decode_scan_pubkey, generate_recipient_pubkeys}; use crate::{ - common::input::{self, TestData}, + common::{ + structs::TestData, + utils::{ + self, decode_input_pub_keys, decode_outpoints, + decode_outputs_to_check, decode_priv_keys, decode_recipients, + get_a_sum_secret_keys, hash_outpoints, compute_ecdh_shared_secret, + }, + }, receiving::{ get_A_sum_public_keys, get_receiving_addresses, scanning, verify_and_calculate_signatures, }, - sending::create_outputs, - utils::hash_outpoints, }; #[test] fn test_with_test_vectors() { - let testdata = input::read_file(); + let testdata = utils::read_file(); for test in testdata { process_test_case(test); @@ -41,18 +45,28 @@ mod tests { let expected_output_addresses: HashSet = expected.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 input_priv_keys = decode_priv_keys(&given.input_priv_keys); - let outputs = - create_outputs(&given.outpoints, &input_priv_keys, &given.recipients).unwrap(); + let outpoints = decode_outpoints(&given.outpoints); - for map in &outputs { - for key in map.keys() { - sending_outputs.insert(key.clone()); + let outpoints_hash = Scalar::from_be_bytes(hash_outpoints(&outpoints)).unwrap(); + + let silent_addresses = decode_recipients(&given.recipients); + + let a_sum = get_a_sum_secret_keys(&input_priv_keys); + + let mut ecdh_shared_secrets: HashMap = HashMap::new(); + for addr in &silent_addresses { + let B_scan = decode_scan_pubkey(addr.to_owned()).unwrap(); + let ecdh_shared_secret = compute_ecdh_shared_secret(a_sum, B_scan, outpoints_hash); + ecdh_shared_secrets.insert(B_scan, ecdh_shared_secret); + } + let outputs = generate_recipient_pubkeys(silent_addresses, ecdh_shared_secrets).unwrap(); + + for output_pubkeys in &outputs { + for pubkey in output_pubkeys.1 { + // TODO check if this is always true + sending_outputs.insert(hex::encode(pubkey.serialize())); } } @@ -65,8 +79,9 @@ mod tests { let receiving_outputs: HashSet = given.outputs.iter().cloned().collect(); - // assert that the sending outputs generated are equal - // to the expected receiving outputs + // assert that the generated sending outputs are a subset + // of the expected receiving outputs + // i.e. all the generated outputs are present assert!(sending_outputs.is_subset(&receiving_outputs)); let b_scan = SecretKey::from_str(&given.scan_priv_key).unwrap(); @@ -81,32 +96,16 @@ mod tests { let set1: HashSet<_> = receiving_addresses.iter().collect(); let set2: HashSet<_> = expected.addresses.iter().collect(); + // check that the receiving addresses generated are equal + // to the expected addresses assert_eq!(set1, set2); // can be even or odd ! - let outputs_to_check: Vec = given - .outputs - .iter() - .map(|x| XOnlyPublicKey::from_str(x).unwrap()) - .collect(); + let outputs_to_check = decode_outputs_to_check(&given.outputs); - let outpoints_hash = hash_outpoints(&given.outpoints).unwrap(); + let outpoints = decode_outpoints(&given.outpoints); - 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 input_pub_keys = decode_input_pub_keys(&given.input_pub_keys); let A_sum = get_A_sum_public_keys(&input_pub_keys).unwrap(); @@ -115,15 +114,8 @@ mod tests { _ => Some(&given.labels), }; - let mut add_to_wallet = scanning( - b_scan, - B_spend, - A_sum, - outpoints_hash, - outputs_to_check, - labels, - ) - .unwrap(); + let mut add_to_wallet = + scanning(b_scan, B_spend, A_sum, outpoints, outputs_to_check, labels).unwrap(); let res = verify_and_calculate_signatures(&mut add_to_wallet, b_spend).unwrap(); assert_eq!(res, expected.outputs);