Skip to content

Commit

Permalink
Move SilentPaymentAddress and Network to utils
Browse files Browse the repository at this point in the history
These structs are not meant to be used for the default sending and
receiving flow, but can still be quite useful. Therefore the utils
module is probably the best place for them.
  • Loading branch information
cygnet3 committed Jun 3, 2024
1 parent 8b699d6 commit a8e0d1b
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 130 deletions.
2 changes: 1 addition & 1 deletion examples/create_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bitcoin::bip32::{DerivationPath, Xpriv};
use bitcoin::secp256k1::Secp256k1;

use silentpayments::receiving::{Label, Receiver};
use silentpayments::Network;
use silentpayments::utils::Network;

fn main() -> Result<(), Box<dyn Error>> {
let secp = Secp256k1::new();
Expand Down
2 changes: 1 addition & 1 deletion examples/find_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn main() -> Result<(), Box<dyn Error>> {
scan_privkey.public_key(&secp),
spend_privkey.public_key(&secp),
change_label,
silentpayments::Network::Testnet,
silentpayments::utils::Network::Testnet,
)?;

let outpoints: Vec<(String, u32)> = tx
Expand Down
8 changes: 0 additions & 8 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::utils::hash::SharedSecretHash;
use crate::Result;
use bitcoin_hashes::Hash;
use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
use serde::{Deserialize, Serialize};

pub(crate) fn calculate_t_n(ecdh_shared_secret: &PublicKey, k: u32) -> Result<SecretKey> {
let hash = SharedSecretHash::from_ecdh_and_k(ecdh_shared_secret, k).to_byte_array();
Expand All @@ -18,10 +17,3 @@ pub(crate) fn calculate_P_n(B_spend: &PublicKey, t_n: Scalar) -> Result<PublicKe

Ok(P_n)
}

#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub enum Network {
Mainnet,
Testnet,
Regtest,
}
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,3 @@ pub use secp256k1;
pub use crate::error::Error;

pub type Result<T> = std::result::Result<T, Error>;
pub type Network = common::Network;
4 changes: 2 additions & 2 deletions src/receiving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use std::{

use crate::{
common::{calculate_P_n, calculate_t_n},
utils::hash::LabelHash,
Error, Network, Result,
utils::{hash::LabelHash, Network},
Error, Result,
};
use bech32::ToBase32;
use bimap::BiMap;
Expand Down
120 changes: 4 additions & 116 deletions src/sending.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,125 +7,13 @@
//! To do this, you can use [`calculate_partial_secret`](crate::utils::sending::calculate_partial_secret) from the `utils` module.
//! See the [tests on github](https://github.com/cygnet3/rust-silentpayments/blob/master/tests/vector_tests.rs)
//! for a concrete example.
use bech32::{FromBase32, ToBase32};

use core::fmt;
use secp256k1::{PublicKey, Secp256k1, SecretKey, XOnlyPublicKey};
use std::collections::HashMap;

use crate::utils::sending::calculate_ecdh_shared_secret;
use crate::{common::calculate_t_n, error::Error, Network, Result};

/// A silent payment address struct.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct SilentPaymentAddress {
version: u8,
scan_pubkey: PublicKey,
m_pubkey: PublicKey,
network: Network,
}

impl SilentPaymentAddress {
pub fn new(
scan_pubkey: PublicKey,
m_pubkey: PublicKey,
network: Network,
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,
network,
version,
})
}

pub fn get_scan_key(&self) -> PublicKey {
self.scan_pubkey
}

pub fn get_spend_key(&self) -> PublicKey {
self.m_pubkey
}

pub fn get_network(&self) -> Network {
self.network
}
}

impl fmt::Display for SilentPaymentAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", <SilentPaymentAddress as Into<String>>::into(*self))
}
}

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 network = match hrp.as_str() {
"sp" => Network::Mainnet,
"tsp" => Network::Testnet,
"sprt" => Network::Regtest,
_ => {
return Err(Error::InvalidAddress(format!(
"Wrong prefix, expected \"sp\", \"tsp\", or \"sprt\", 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, network, version)
}
}

impl TryFrom<String> for SilentPaymentAddress {
type Error = Error;

fn try_from(addr: String) -> Result<Self> {
addr.as_str().try_into()
}
}

impl From<SilentPaymentAddress> for String {
fn from(val: SilentPaymentAddress) -> Self {
let hrp = match val.network {
Network::Testnet => "tsp",
Network::Regtest => "sprt",
Network::Mainnet => "sp",
};

let version = bech32::u5::try_from_u8(val.version).unwrap();

let B_scan_bytes = val.scan_pubkey.serialize();
let B_m_bytes = val.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()
}
}
use crate::utils::SilentPaymentAddress;
use crate::{common::calculate_t_n, Result};

/// Create outputs for a given set of silent payment recipients and their corresponding shared secrets.
///
Expand Down Expand Up @@ -159,7 +47,7 @@ pub fn generate_recipient_pubkeys(
HashMap::new();
for address in recipients {
let address: SilentPaymentAddress = address.try_into()?;
let B_scan = address.scan_pubkey;
let B_scan = address.get_scan_key();

if let Some((_, payments)) = silent_payment_groups.get_mut(&B_scan) {
payments.push(address);
Expand All @@ -180,7 +68,7 @@ pub fn generate_recipient_pubkeys(
let t_n = calculate_t_n(&ecdh_shared_secret, n)?;

let res = t_n.public_key(&secp);
let reskey = res.combine(&addr.m_pubkey)?;
let reskey = res.combine(&addr.get_spend_key())?;
let (reskey_xonly, _) = reskey.x_only_public_key();

let entry = result.entry(addr.into()).or_default();
Expand Down
5 changes: 5 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ pub mod receiving;
#[cfg(feature = "sending")]
pub mod sending;

mod common;

pub use common::Network;
pub use common::SilentPaymentAddress;

/// [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)-defined 'Nothing Up My Sleeve' point.
pub const NUMS_H: [u8; 32] = [
0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e,
Expand Down
129 changes: 129 additions & 0 deletions src/utils/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use core::fmt;

use crate::Error;
use crate::Result;
use bech32::{FromBase32, ToBase32};
use secp256k1::PublicKey;
use serde::{Deserialize, Serialize};

/// The network format used for this silent payment address.
///
/// There are three network types: Mainnet (`sp1..`), Testnet (`tsp1..`), and Regtest (`sprt1..`).
/// Signet uses the same network type as Testnet.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
pub enum Network {
Mainnet,
Testnet,
Regtest,
}

/// A silent payment address struct that can be used to deserialize a silent payment address string.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct SilentPaymentAddress {
version: u8,
scan_pubkey: PublicKey,
m_pubkey: PublicKey,
network: Network,
}

impl SilentPaymentAddress {
pub fn new(
scan_pubkey: PublicKey,
m_pubkey: PublicKey,
network: Network,
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,
network,
version,
})
}

pub fn get_scan_key(&self) -> PublicKey {
self.scan_pubkey
}

pub fn get_spend_key(&self) -> PublicKey {
self.m_pubkey
}

pub fn get_network(&self) -> Network {
self.network
}
}

impl fmt::Display for SilentPaymentAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", <SilentPaymentAddress as Into<String>>::into(*self))
}
}

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 network = match hrp.as_str() {
"sp" => Network::Mainnet,
"tsp" => Network::Testnet,
"sprt" => Network::Regtest,
_ => {
return Err(Error::InvalidAddress(format!(
"Wrong prefix, expected \"sp\", \"tsp\", or \"sprt\", 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, network, version)
}
}

impl TryFrom<String> for SilentPaymentAddress {
type Error = Error;

fn try_from(addr: String) -> Result<Self> {
addr.as_str().try_into()
}
}

impl From<SilentPaymentAddress> for String {
fn from(val: SilentPaymentAddress) -> Self {
let hrp = match val.network {
Network::Testnet => "tsp",
Network::Regtest => "sprt",
Network::Mainnet => "sp",
};

let version = bech32::u5::try_from_u8(val.version).unwrap();

let B_scan_bytes = val.scan_pubkey.serialize();
let B_m_bytes = val.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()
}
}
2 changes: 1 addition & 1 deletion tests/vector_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ mod tests {
calculate_ecdh_shared_secret, calculate_tweak_data, get_pubkey_from_input, is_p2tr,
},
sending::calculate_partial_secret,
Network,
},
Network,
};
use std::{collections::HashSet, io::Cursor, str::FromStr};

Expand Down

0 comments on commit a8e0d1b

Please sign in to comment.