Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change #67

Merged
merged 4 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
90 changes: 40 additions & 50 deletions src/receiving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Label, PublicKey>,
pub is_testnet: bool,
}
Expand Down Expand Up @@ -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()
}
Expand All @@ -209,6 +209,7 @@ struct ReceiverHelper {
is_testnet: bool,
scan_pubkey: SerializablePubkey,
spend_pubkey: SerializablePubkey,
change_label: String,
labels: SerializableBiMap,
}

Expand All @@ -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,
})
}
Expand All @@ -233,6 +235,7 @@ impl Receiver {
version: u32,
scan_pubkey: PublicKey,
spend_pubkey: PublicKey,
change_label: Label,
is_testnet: bool,
) -> Result<Self> {
let labels: BiMap<Label, PublicKey> = BiMap::new();
Expand All @@ -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<bool> {
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())
Expand Down Expand Up @@ -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 {
cygnet3 marked this conversation as resolved.
Show resolved Hide resolved
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
Expand All @@ -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
///
Expand All @@ -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<XOnlyPublicKey>,
) -> Result<HashMap<Label, HashMap<XOnlyPublicKey, Scalar>>> {
) -> Result<HashMap<Option<Label>, HashMap<XOnlyPublicKey, Scalar>>> {
let secp = secp256k1::Secp256k1::new();

let mut found: HashMap<Label, HashMap<XOnlyPublicKey, Scalar>> = HashMap::new();
let mut found: HashMap<Option<Label>, HashMap<XOnlyPublicKey, Scalar>> = HashMap::new();
let mut n_found: u32 = 0;
let mut n: u32 = 0;
while n_found == n {
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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<XOnlyPublicKey>,
) -> Result<HashMap<XOnlyPublicKey, Scalar>> {
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.
Expand Down
10 changes: 8 additions & 2 deletions tests/vector_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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();

Expand All @@ -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<Scalar> = scanned_outputs_received
Expand Down
Loading