Improve sender interface (#10)

* Refactor tests: add utils.rs for test helper functions

* Refactor sending

* Add SilentPaymentAddress struct

* Use structs instead of tuples as func arguments

* Change create_outputs function and remove amounts

* Use is_testnet in SilentPaymentAddress struct

* Provide pre-computed tweak data to create_outputs function

* Git rebase main

* Provide ecdh shared secret as argument to create_output

* Rename create_outputs -> generate_recipient_pubkeys
This commit is contained in:
cygnet 2023-08-15 20:06:58 +02:00 committed by GitHub
parent cd09955b30
commit c3b8d65e03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 395 additions and 189 deletions

40
src/error.rs Normal file
View File

@ -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<hex::FromHexError> for Error {
fn from(e: hex::FromHexError) -> Self {
Error::InvalidLabel(e.to_string())
}
}
impl From<bech32::Error> for Error {
fn from(e: bech32::Error) -> Self {
Error::InvalidAddress(e.to_string())
}
}
impl From<secp256k1::Error> for Error {
fn from(e: secp256k1::Error) -> Self {
Error::Secp256k1Error(e)
}
}
impl From<secp256k1::scalar::OutOfRangeError> for Error {
fn from(e: secp256k1::scalar::OutOfRangeError) -> Self {
Error::OutOfRangeError(e)
}
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::IOError(e)
}
}

View File

@ -1,5 +1,6 @@
#![allow(non_snake_case)]
mod error;
pub mod receiving;
pub mod sending;
pub mod structs;
pub mod utils;
mod utils;

View File

@ -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<u8>,
) -> Result<String> {
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<Outpoint>,
outputs_to_check: Vec<XOnlyPublicKey>,
labels: Option<&HashMap<String, String>>,
) -> Result<Vec<ScannedOutput>> {
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<ScannedOutput> = 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<OutputWithSignature> = 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)?;

View File

@ -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<SecretKey> {
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<Self> {
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<Self> {
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::<u8>::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<String> for SilentPaymentAddress {
type Error = Error;
fn try_from(addr: String) -> Result<Self> {
addr.as_str().try_into()
}
}
impl Into<String> 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<String>,
ecdh_shared_secrets: HashMap<PublicKey, PublicKey>,
) -> Result<HashMap<String, Vec<XOnlyPublicKey>>> {
let secp = Secp256k1::new();
let mut negated_keys: Vec<SecretKey> = vec![];
let mut silent_payment_groups: HashMap<PublicKey, (PublicKey, Vec<SilentPaymentAddress>)> =
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<SecretKey> = tail
.iter()
.fold(Ok(*head), |acc: Result<SecretKey>, &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::<u8>::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<Vec<HashMap<String, f32>>> {
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<PublicKey, Vec<(PublicKey, f32)>> = 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<HashMap<String, f32>> = vec![];
for (B_scan, B_m_values) in silent_payment_groups.into_iter() {
let mut result: HashMap<String, Vec<XOnlyPublicKey>> = 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<u8> = 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<String, f32> = 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<PublicKey> {
let address: SilentPaymentAddress = silent_payment_address.try_into()?;
Ok(address.scan_pubkey)
}

View File

@ -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,
}

View File

@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
use crate::{error::Error, structs::Outpoint};
pub type Result<T> = std::result::Result<T, Error>;
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<u8> {
u.to_be_bytes().into()
}
pub fn hash_outpoints(sending_data: &Vec<(String, u32)>) -> Result<[u8; 32]> {
pub fn hash_outpoints(sending_data: &HashSet<Outpoint>) -> Result<[u8; 32]> {
let mut outpoints: Vec<Vec<u8>> = 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<u8> = 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())

View File

@ -1 +1,2 @@
pub mod input;
pub mod structs;
pub mod utils;

View File

@ -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<String, f32>,
}
impl From<HashMap<String, f32>> for ComparableHashMap {
fn from(map: HashMap<String, f32>) -> 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<H: Hasher>(&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<TestData> {
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()
}

118
tests/common/utils.rs Normal file
View File

@ -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<TestData> {
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<Outpoint> {
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<String>) -> Vec<PublicKey> {
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<String>) -> Vec<XOnlyPublicKey> {
outputs
.iter()
.map(|x| XOnlyPublicKey::from_str(x).unwrap())
.collect()
}
pub fn decode_recipients(recipients: &Vec<(String, f32)>) -> Vec<String> {
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<SecretKey> = 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<Outpoint>) -> [u8; 32] {
let mut outpoints: Vec<Vec<u8>> = vec![];
for outpoint in sending_data {
let txid = outpoint.txid;
let vout = outpoint.vout;
let mut bytes: Vec<u8> = 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()
}

View File

@ -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<String> =
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<PublicKey, PublicKey> = 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<String> = 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<XOnlyPublicKey> = 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<PublicKey> = 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);