diff --git a/src/lib.rs b/src/lib.rs index 8b52da0..3f15ca0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod sending; #[cfg(feature = "utils")] pub mod utils; +pub use bitcoin_hashes; pub use secp256k1; pub use crate::error::Error; diff --git a/src/receiving.rs b/src/receiving.rs index 10afb01..31dfca3 100644 --- a/src/receiving.rs +++ b/src/receiving.rs @@ -16,8 +16,6 @@ use serde::{ Deserialize, Deserializer, Serialize, }; -pub const NULL_LABEL: Label = Label { s: Scalar::ZERO }; - #[derive(Eq, PartialEq, Clone)] pub struct Label { s: Scalar, @@ -94,6 +92,7 @@ pub struct Receiver { version: u8, scan_pubkey: PublicKey, spend_pubkey: PublicKey, + change_label: Label, // To be able to tell which label is the change labels: BiMap, pub is_testnet: bool, } @@ -198,6 +197,7 @@ impl Serialize for Receiver { "spend_pubkey", &SerializablePubkey(self.spend_pubkey.serialize()), )?; + state.serialize_field("change_label", &self.change_label.as_string())?; state.serialize_field("labels", &SerializableBiMap(self.labels.clone()))?; state.end() } @@ -209,6 +209,7 @@ struct ReceiverHelper { is_testnet: bool, scan_pubkey: SerializablePubkey, spend_pubkey: SerializablePubkey, + change_label: String, labels: SerializableBiMap, } @@ -223,6 +224,7 @@ impl<'de> Deserialize<'de> for Receiver { is_testnet: helper.is_testnet, scan_pubkey: PublicKey::from_slice(&helper.scan_pubkey.0).unwrap(), spend_pubkey: PublicKey::from_slice(&helper.spend_pubkey.0).unwrap(), + change_label: Label::try_from(helper.change_label).unwrap(), labels: helper.labels.0, }) } @@ -233,6 +235,7 @@ impl Receiver { version: u32, scan_pubkey: PublicKey, spend_pubkey: PublicKey, + change_label: Label, is_testnet: bool, ) -> Result { let labels: BiMap = BiMap::new(); @@ -244,23 +247,32 @@ impl Receiver { )); } - Ok(Receiver { + let mut receiver = Receiver { version: version as u8, scan_pubkey, spend_pubkey, + change_label: change_label.clone(), labels, is_testnet, - }) + }; + + // This check that the change_label produces a valid key at each step + receiver.add_label(change_label)?; + + Ok(receiver) } /// Takes a Label and adds it to the list of labels that this recipient uses. /// Returns a bool on success, `true` if the label was new, `false` if it already existed in our list. pub fn add_label(&mut self, label: Label) -> Result { - let secp = Secp256k1::new(); + let secp = Secp256k1::signing_only(); let m = SecretKey::from_slice(&label.as_inner().to_be_bytes())?; let mG = m.public_key(&secp); + // check that the combined key with spend_key is valid + mG.combine(&self.spend_pubkey)?; + let old = self.labels.insert(label, mG); Ok(!old.did_overwrite()) @@ -297,6 +309,22 @@ impl Receiver { } } + /// Get the silent payment change address for this Receiver. This is the + /// static address associated with the change label, as described + /// in the BIP. Wallets can create silent payment-native change addresses + /// by sending to this static change address, much like sending to a normal + /// silent payment address. + /// Important note: this address should never be shown to the user! + pub fn get_change_address(&self) -> String { + let sk = SecretKey::from_slice(&self.change_label.as_inner().to_be_bytes()) + .expect("Unexpected invalid change label"); + let pk = sk.public_key(&Secp256k1::signing_only()); + let B_m = pk + .combine(&self.spend_pubkey) + .expect("Unexpected invalid pubkey"); + self.encode_silent_payment_address(B_m) + } + /// Get the bech32m-encoded silent payment address. /// /// # Returns @@ -312,6 +340,7 @@ impl Receiver { /// /// * `ecdh_shared_secret` - The ECDH shared secret between sender and recipient as a PublicKey, the result of elliptic-curve multiplication of `(outpoints_hash * sum_inputs_pubkeys) * scan_private_key`. /// * `pubkeys_to_check` - A `HashSet` of public keys of all (unspent) taproot output of the transaction. + /// * `with_labels` - a bool to indicate wether we want to scan for labels too, including change /// /// # Returns /// @@ -323,14 +352,14 @@ impl Receiver { /// /// * One of the public keys to scan can't be parsed into a valid x-only public key. /// * An error occurs during elliptic curve computation. This may happen if a sender is being malicious. (?) - pub fn scan_transaction_with_labels( + pub fn scan_transaction( &self, ecdh_shared_secret: &PublicKey, pubkeys_to_check: Vec, - ) -> Result>> { + ) -> Result, HashMap>> { let secp = secp256k1::Secp256k1::new(); - let mut found: HashMap> = HashMap::new(); + let mut found: HashMap, HashMap> = HashMap::new(); let mut n_found: u32 = 0; let mut n: u32 = 0; while n_found == n { @@ -340,10 +369,10 @@ impl Receiver { if pubkeys_to_check.iter().any(|p| p.eq(&P_n_xonly)) { n_found += 1; found - .entry(NULL_LABEL) + .entry(None) .or_insert_with(HashMap::new) .insert(P_n_xonly, t_n.into()); - } else if !self.labels.is_empty() { + } else { // We subtract P_n from each outputs to check and see if match a public key in our label list 'outer: for p in &pubkeys_to_check { let even_output = p.public_key(Parity::Even); @@ -356,7 +385,7 @@ impl Receiver { n_found += 1; let t_n_label = t_n.add_tweak(label.as_inner())?; found - .entry(label.clone()) + .entry(Some(label.clone())) .or_insert_with(HashMap::new) .insert(*p, t_n_label.into()); break 'outer; @@ -369,45 +398,6 @@ impl Receiver { Ok(found) } - /// Scans a transaction for outputs belonging to us. - /// Note: this function is only for wallets that don't use labels! - /// If this silent payment wallet uses labels, use `scan_transaction_with_labels` instead. - /// - /// # Arguments - /// - /// * `tweak_data` - The tweak data for the transaction as a PublicKey, the result of elliptic-curve multiplication of `outpoints_hash * A`. - /// * `pubkeys_to_check` - A `Vec` of public keys of all (unspent) taproot output of the transaction. - /// - /// # Returns - /// - /// If successful, the function returns a `Result` wrapping a `HashMap` that maps the given outputs to private key tweaks. A resulting `HashMap` of length 0 implies none of the outputs are owned by us. - /// - /// # Errors - /// - /// This function will return an error if: - /// - /// * One of the public keys to scan can't be parsed into a valid x-only public key. - /// * An error occurs during elliptic curve computation. This may happen if a sender is being malicious. (?) - pub fn scan_transaction( - &self, - tweak_data: &PublicKey, - pubkeys_to_check: Vec, - ) -> Result> { - if !self.labels.is_empty() { - return Err(Error::GenericError( - "This function should only be used by wallets without labels; use scan_transaction_with_labels instead".to_owned(), - )); - } - - // re-use scan_transaction_with_labels function - let mut map = self.scan_transaction_with_labels(tweak_data, pubkeys_to_check)?; - - match map.remove(&NULL_LABEL) { - Some(res) => Ok(res), - None => Ok(HashMap::new()), - } - } - /// Get the Script byte vector from a transaction's tweak data. /// Using the tweak data, this function will calculate the resulting script, given the assumption that this transaction is a payment to us. /// This Script can be useful for BIP158 block filters. diff --git a/tests/vector_tests.rs b/tests/vector_tests.rs index 04ee840..3cc7e04 100644 --- a/tests/vector_tests.rs +++ b/tests/vector_tests.rs @@ -122,7 +122,9 @@ mod tests { let B_spend = b_spend.public_key(&secp); let B_scan = b_scan.public_key(&secp); - let mut sp_receiver = Receiver::new(0, B_scan, B_spend, IS_TESTNET).unwrap(); + let change_label = LabelHash::from_b_scan_and_m(b_scan, 0).to_scalar(); + let mut sp_receiver = + Receiver::new(0, B_scan, B_spend, change_label.into(), IS_TESTNET).unwrap(); let outputs_to_check = decode_outputs_to_check(&given.outputs); @@ -171,6 +173,10 @@ mod tests { .insert(sp_receiver.get_receiving_address_for_label(label).unwrap()); } + if !&given.labels.iter().any(|l| *l == 0) { + receiving_addresses.remove(&sp_receiver.get_change_address()); + } + let set1: HashSet<_> = receiving_addresses.iter().collect(); let set2: HashSet<_> = expected.addresses.iter().collect(); @@ -182,7 +188,7 @@ mod tests { let shared_secret = recipient_calculate_shared_secret(tweak_data, b_scan).unwrap(); let scanned_outputs_received = sp_receiver - .scan_transaction_with_labels(&shared_secret, outputs_to_check) + .scan_transaction(&shared_secret, outputs_to_check) .unwrap(); let key_tweaks: Vec = scanned_outputs_received