From 0d28b6173057a108b0009c23addbe3de900b5118 Mon Sep 17 00:00:00 2001 From: cygnet <131168104+cygnet3@users.noreply.github.com> Date: Tue, 22 Aug 2023 21:56:08 +0200 Subject: [PATCH] Improve receiver interface (#16) * Copy recipient code structure from Sosthene00 fork * Move SilentPayment struct to receiving * Provide shared_secret as input argument for scan_for_outputs Also refactors a number of things to enable this change: calculate A_sum and outpoints_hash in the test code instead of the library, remove the obsolete hash_outpoints function from the library move calculate_P_n and calculate_t_n to utils.rs. * Add debug trait implementation for Label * Refactoring * Add get_receiving_address function for getting sp-address per label * Refactor scan_for_outputs and rename to scan_transaction * Provide tweak data to scan_transaction function * Expand README.me --- README.md | 31 ++- src/lib.rs | 8 +- src/receiving.rs | 435 ++++++++++++++++++++++++---------------- src/sending.rs | 3 +- src/structs.rs | 20 -- src/utils.rs | 76 ++++--- tests/common/structs.rs | 14 +- tests/common/utils.rs | 60 +++++- tests/vector_tests.rs | 99 +++++---- 9 files changed, 471 insertions(+), 275 deletions(-) delete mode 100644 src/structs.rs diff --git a/README.md b/README.md index b501542..28e3ed1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,32 @@ # Silent Payments -This repo is a rust implementation of BIP352: Silent Payments. -This BIP is still under development, and this repo is by no means ready for real use yet. -At this point, the repo is no more than a rust rewrite of the `reference.py` python reference implementation. +A rust implementation of BIP352: Silent Payments. + +Although this library passes all the tests provided in the silent payment BIP, +it is still very new, so be careful when using this with real funds. + +There are two parts to this library: a sender part and a recipient part. + +## Sender + +For sending to a silent payment address, you can call the `sender::generate_recipient_pubkeys` function. +This function takes a `recipient: Vec` as an argument, where the `String` is a bech32m encoded silent payment address (`sp1q...` or `tsp1q...`). + +This function additionally takes a `ecdh_shared_secrets: HashMap` argument, which maps a Spend key to a shared secret. +Since this shared secret derivation requires secret data, this library expects the user to provide the pre-computed result. + +See the `tests/vector_tests.rs` and `tests/common/utils.rs` files for an example of how to compute the shared secrets. + +## Recipient + +For receiving silent payments. We have use a struct called `recipient::SilentPayment`. +After creating this struct with a spending and scanning secret key, +you can call the `scan_transaction` function to look for any outputs in a transaction belonging to you. + +The library also supports labels. You can optionally add labels before scanning by using the `add_label` function. + +## Tests The `tests/resources` folder contains a copy of the test vectors as of August 4th 2023. -You can test the code using the test vectors by running `cargo test` +You can test the code using the test vectors by running `cargo test`. diff --git a/src/lib.rs b/src/lib.rs index 89efc58..2be57b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,10 @@ -#![allow(non_snake_case)] +#![allow(dead_code, non_snake_case)] + mod error; pub mod receiving; pub mod sending; -pub mod structs; mod utils; + +pub use crate::error::Error; + +pub type Result = std::result::Result; diff --git a/src/receiving.rs b/src/receiving.rs index 369eb87..e4adecc 100644 --- a/src/receiving.rs +++ b/src/receiving.rs @@ -1,212 +1,299 @@ -use bech32::ToBase32; - -use secp256k1::{hashes::Hash, Message, PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey}; use std::{ collections::{HashMap, HashSet}, - str::FromStr, + fmt, + hash::{Hash, Hasher}, }; use crate::{ - error::Error, - structs::{Outpoint, OutputWithSignature, ScannedOutput}, - utils::{hash_outpoints, ser_uint32, Result}, + utils::{calculate_P_n, calculate_t_n, insert_new_key}, + Error, }; +use bech32::ToBase32; +use secp256k1::{Parity, PublicKey, Scalar, Secp256k1, SecretKey, XOnlyPublicKey}; -pub fn get_receiving_addresses( - B_scan: PublicKey, - B_spend: PublicKey, - labels: &HashMap, -) -> Result> { - let mut receiving_addresses: Vec = vec![]; - 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, - )?); +use crate::Result; + +pub(crate) const NULL_LABEL: Label = Label { s: Scalar::ZERO }; + +#[derive(Eq, PartialEq, Clone)] +pub struct Label { + s: Scalar, +} + +impl Label { + pub fn into_inner(self) -> Scalar { + self.s } - Ok(receiving_addresses) + pub fn as_inner(&self) -> &Scalar { + &self.s + } + + pub fn as_string(&self) -> String { + hex::encode(self.as_inner().to_be_bytes()) + } } -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) +impl fmt::Debug for Label { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.as_string()) + } } -fn encode_silent_payment_address( - B_scan: PublicKey, - B_m: PublicKey, - hrp: Option<&str>, - version: Option, -) -> Result { - let hrp = hrp.unwrap_or("sp"); - 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(); - - let mut data = [B_scan_bytes, B_m_bytes].concat().to_base32(); - - data.insert(0, version); - - Ok(bech32::encode(hrp, data, bech32::Variant::Bech32m)?) +impl Hash for Label { + fn hash(&self, state: &mut H) { + let bytes = self.s.to_be_bytes(); + bytes.hash(state); + } } -fn create_labeled_silent_payment_address( - B_scan: PublicKey, - B_spend: PublicKey, - m: &String, - hrp: Option<&str>, - version: Option, -) -> Result { - 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(); - 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) +impl From for Label { + fn from(s: Scalar) -> Self { + Label { s } + } } -fn calculate_P_n(B_spend: &PublicKey, t_n: [u8; 32]) -> Result { - let secp = Secp256k1::new(); +impl TryFrom for Label { + type Error = Error; - 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)?; - - Ok(P_n) + fn try_from(s: String) -> Result