Skip to content

Commit 24f1ff9

Browse files
committed
Add serde and encode features, gate the code
1 parent 8e74226 commit 24f1ff9

8 files changed

Lines changed: 195 additions & 16 deletions

File tree

Cargo.toml

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,21 @@ name = "silentpayments"
1616
crate-type = ["lib"]
1717

1818
[features]
19-
default = ["sending", "receiving"]
20-
sending = []
21-
receiving = []
19+
default = ["encode", "sending", "receiving"]
20+
encode = ["dep:bech32"]
21+
serde = ["dep:serde"]
22+
sending = ["dep:bitcoin_hashes", "dep:hex", "encode"]
23+
receiving = ["dep:bitcoin_hashes", "dep:hex", "dep:bimap", "serde", "encode"]
2224

2325
[dependencies]
2426
secp256k1 = {version = "0.28.1", features = ["rand"] }
25-
hex = "0.4"
26-
bech32 = "0.9"
27-
bimap = "0.6"
28-
serde = { version = "1.0", features = ["derive"] }
29-
serde_json = "1.0"
30-
bitcoin_hashes = "0.13.0"
27+
hex = { version = "0.4", optional = true }
28+
bech32 = { version = "0.9", optional = true }
29+
bimap = { version = "0.6", optional = true }
30+
serde = { version = "1.0", features = ["derive"], optional = true }
31+
bitcoin_hashes = { version = "0.13.0", optional = true }
3132

3233
[dev-dependencies]
3334
rust-bip39 = { version = "1.0.0", features = ["rand"] }
3435
bitcoin ={ version = "0.31.1", features = ["serde"] }
36+
serde_json = "1.0"

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,47 @@ In the future, the library will probably be expanded to rely on structs from rus
1414

1515
The library is split up in two parts: sending and receiving.
1616

17+
## Feature Flags
18+
19+
This library offers granular feature flags to minimize dependencies for different use cases:
20+
21+
- **default**: Enables all features (`encode`, `sending`, `receiving`)
22+
- **encode**: Enables string encoding/decoding for `SilentPaymentAddress` (adds `bech32` dependency)
23+
- **serde**: Enables serde serialization/deserialization for types (adds `serde` dependency)
24+
- **sending**: Enables sending functionality (adds `bitcoin_hashes`, `hex` dependencies)
25+
- **receiving**: Enables receiving functionality (adds `bitcoin_hashes`, `hex`, `bimap`, `serde` dependencies)
26+
27+
### Minimal Usage
28+
29+
If you only need the type definitions (`Network` and `SilentPaymentAddress`) without any protocol functionality:
30+
31+
```toml
32+
[dependencies]
33+
silentpayments = { version = "0.4", default-features = false }
34+
```
35+
36+
This configuration only pulls in `secp256k1` as a dependency, significantly reducing the dependency tree for applications that only need to work with silent payment addresses without implementing the full protocol.
37+
38+
**Bring Your Own Parser**: Even without the `encode` feature, you can construct a `SilentPaymentAddress` using `SilentPaymentAddress::new()` if you parse the bech32 yourself. This is useful if your application already has a bech32 parser and you want to avoid duplicate dependencies. The constructor documentation includes the complete bech32 format specification.
39+
40+
### Custom Feature Combinations
41+
42+
You can enable only the features you need:
43+
44+
```toml
45+
# Just types and string encoding (no protocol implementation)
46+
silentpayments = { version = "0.4", default-features = false, features = ["encode"] }
47+
48+
# Types with serde support (no protocol or encoding)
49+
silentpayments = { version = "0.4", default-features = false, features = ["serde"] }
50+
51+
# Only sending capability
52+
silentpayments = { version = "0.4", default-features = false, features = ["sending"] }
53+
54+
# Only receiving capability
55+
silentpayments = { version = "0.4", default-features = false, features = ["receiving"] }
56+
```
57+
1758
## Sending
1859

1960
For sending to a silent payment address, you can call the `sender::generate_recipient_pubkeys` function.

examples/custom_parser.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/// Example showing how to construct a SilentPaymentAddress without the `encode` feature.
2+
///
3+
/// This is useful if your application already has a bech32 parser and you want to
4+
/// avoid duplicate dependencies. You can parse the address yourself and then
5+
/// construct the type using the public constructor.
6+
///
7+
/// To run this example:
8+
/// ```bash
9+
/// cargo run --example custom_parser --no-default-features
10+
/// ```
11+
12+
fn main() {
13+
use secp256k1::{PublicKey, Secp256k1, SecretKey};
14+
use silentpayments::{Network, SilentPaymentAddress};
15+
16+
// Example: Simulating what you'd get after parsing bech32 yourself
17+
// In a real application, you would:
18+
// 1. Parse the bech32m string (e.g., "sp1...")
19+
// 2. Extract the HRP to determine the network
20+
// 3. Decode the data part to get version + 33-byte scan key + 33-byte spend key
21+
// 4. Deserialize the pubkeys from those bytes
22+
// 5. Construct the SilentPaymentAddress using the constructor
23+
24+
// For this example, we'll generate valid pubkeys
25+
let secp = Secp256k1::new();
26+
let scan_secret = SecretKey::from_slice(&[0x01; 32]).expect("valid key");
27+
let spend_secret = SecretKey::from_slice(&[0x02; 32]).expect("valid key");
28+
29+
let scan_pubkey = PublicKey::from_secret_key(&secp, &scan_secret);
30+
let spend_pubkey = PublicKey::from_secret_key(&secp, &spend_secret);
31+
32+
// Construct the SilentPaymentAddress without needing the `encode` feature
33+
let address = SilentPaymentAddress::new(
34+
scan_pubkey,
35+
spend_pubkey,
36+
Network::Mainnet,
37+
0, // version
38+
)
39+
.expect("Failed to create address");
40+
41+
println!("Successfully created SilentPaymentAddress without encode feature!");
42+
println!("Network: {:?}", address.get_network());
43+
println!("Version: {}", address.get_version());
44+
println!("Scan key: {:?}", address.get_scan_key());
45+
println!("Spend key: {:?}", address.get_spend_key());
46+
47+
println!("\n✅ This example compiled and ran with ZERO features enabled!");
48+
println!(" Only dependencies: secp256k1, rand, rand_core, secp256k1-sys");
49+
}
50+

src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ impl fmt::Display for Error {
3131

3232
impl std::error::Error for Error {}
3333

34+
#[cfg(any(feature = "sending", feature = "receiving"))]
3435
impl From<hex::FromHexError> for Error {
3536
fn from(e: hex::FromHexError) -> Self {
3637
Error::InvalidLabel(e.to_string())
3738
}
3839
}
3940

41+
#[cfg(feature = "encode")]
4042
impl From<bech32::Error> for Error {
4143
fn from(e: bech32::Error) -> Self {
4244
Error::InvalidAddress(e.to_string())

src/lib.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
11
//! A rust implementation of BIP352: Silent Payments. This library
22
//! can be used to add silent payment support to wallets.
33
//!
4-
//! This library is split up in two parts: sending and receiving.
5-
//! Either of these can be implemented independently using
6-
//! the `sending` or `receiving` features.
4+
//! ## Feature Flags
5+
//!
6+
//! This library offers granular feature flags to minimize dependencies:
7+
//!
8+
//! - **default**: Enables `encode`, `sending`, and `receiving` features
9+
//! - **encode**: Enables string encoding/decoding for `SilentPaymentAddress` (requires `bech32`)
10+
//! - **serde**: Enables serde serialization/deserialization for types
11+
//! - **sending**: Enables sending functionality (requires `bitcoin_hashes`, `hex`, and `encode`)
12+
//! - **receiving**: Enables receiving functionality (requires `bitcoin_hashes`, `hex`, `bimap`, `serde`, and `encode`)
13+
//!
14+
//! ### Minimal Usage
15+
//!
16+
//! If you only need the type definitions (`Network` and `SilentPaymentAddress`) without
17+
//! any additional functionality, you can disable all default features:
18+
//!
19+
//! ```toml
20+
//! [dependencies]
21+
//! silentpayments = { version = "0.4", default-features = false }
22+
//! ```
23+
//!
24+
//! This will only pull in `secp256k1` as a dependency, giving you access to the core types
25+
//! without any encoding, serialization, or protocol functionality.
26+
//!
27+
//! **Note**: Even without the `encode` feature, you can still construct a `SilentPaymentAddress`
28+
//! from its components using `SilentPaymentAddress::new()`. This allows you to use your own
29+
//! bech32 parser (if your application already has one) and avoid duplicate dependencies.
30+
//! See the `SilentPaymentAddress::new()` documentation for the bech32 format specification.
731
//!
832
//! ## Examples
933
//!
@@ -22,6 +46,7 @@ pub mod receiving;
2246
pub mod sending;
2347
pub mod utils;
2448

49+
#[cfg(any(feature = "sending", feature = "receiving"))]
2550
pub use bitcoin_hashes;
2651
pub use secp256k1;
2752

src/receiving.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ impl Serialize for SerializablePubkey {
148148
S: serde::Serializer,
149149
{
150150
let mut seq = serializer.serialize_tuple(self.0.len())?;
151-
for element in self.0.as_ref() {
151+
for element in &self.0[..] {
152152
seq.serialize_element(element)?;
153153
}
154154
seq.end()

src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! This module contains functions that are more 'high-level'
44
//! than the basic sending and receiving logic.
5+
#[cfg(any(feature = "sending", feature = "receiving"))]
56
pub(crate) mod hash;
67
#[cfg(feature = "receiving")]
78
pub mod receiving;

src/utils/common.rs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
1+
#[cfg(feature = "encode")]
12
use core::fmt;
23

4+
#[cfg(any(feature = "sending", feature = "receiving"))]
35
use crate::utils::hash::SharedSecretHash;
6+
#[cfg(any(feature = "sending", feature = "receiving"))]
7+
use bitcoin_hashes::Hash;
48
use crate::Error;
59
use crate::Result;
10+
#[cfg(feature = "encode")]
611
use bech32::{FromBase32, ToBase32};
7-
use bitcoin_hashes::Hash;
8-
use secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey};
12+
#[cfg(any(feature = "sending", feature = "receiving"))]
13+
use secp256k1::{Scalar, Secp256k1, SecretKey};
14+
use secp256k1::PublicKey;
15+
#[cfg(all(feature = "serde", feature = "encode"))]
916
use serde::ser::Serializer;
17+
#[cfg(all(feature = "serde", feature = "encode"))]
1018
use serde::Deserializer;
19+
#[cfg(feature = "serde")]
1120
use serde::{Deserialize, Serialize};
1221

22+
#[cfg(any(feature = "sending", feature = "receiving"))]
1323
pub(crate) fn calculate_t_n(ecdh_shared_secret: &PublicKey, k: u32) -> Result<SecretKey> {
1424
let hash = SharedSecretHash::from_ecdh_and_k(ecdh_shared_secret, k).to_byte_array();
1525
let sk = SecretKey::from_slice(&hash)?;
1626

1727
Ok(sk)
1828
}
1929

30+
#[cfg(any(feature = "sending", feature = "receiving"))]
2031
pub(crate) fn calculate_P_n(B_spend: &PublicKey, t_n: Scalar) -> Result<PublicKey> {
2132
let secp = Secp256k1::new();
2233

@@ -29,7 +40,8 @@ pub(crate) fn calculate_P_n(B_spend: &PublicKey, t_n: Scalar) -> Result<PublicKe
2940
///
3041
/// There are three network types: Mainnet (`sp1..`), Testnet (`tsp1..`), and Regtest (`sprt1..`).
3142
/// Signet uses the same network type as Testnet.
32-
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Deserialize, Serialize)]
43+
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
44+
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
3345
pub enum Network {
3446
Mainnet,
3547
Testnet,
@@ -69,6 +81,7 @@ pub struct SilentPaymentAddress {
6981
network: Network,
7082
}
7183

84+
#[cfg(all(feature = "serde", feature = "encode"))]
7285
impl Serialize for SilentPaymentAddress {
7386
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
7487
where
@@ -79,6 +92,7 @@ impl Serialize for SilentPaymentAddress {
7992
}
8093
}
8194

95+
#[cfg(all(feature = "serde", feature = "encode"))]
8296
impl<'de> Deserialize<'de> for SilentPaymentAddress {
8397
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
8498
where
@@ -91,6 +105,38 @@ impl<'de> Deserialize<'de> for SilentPaymentAddress {
91105
}
92106

93107
impl SilentPaymentAddress {
108+
/// Construct a `SilentPaymentAddress` from its component parts.
109+
///
110+
/// This constructor is always available, even without the `encode` feature.
111+
/// If you have your own bech32 parser, you can use it to extract the components
112+
/// and then construct the address using this method.
113+
///
114+
/// # Bech32 Format (for external parsers)
115+
///
116+
/// Silent payment addresses use bech32m encoding with the following structure:
117+
/// - **HRP (Human Readable Part)**:
118+
/// - Mainnet: `"sp"`
119+
/// - Testnet/Signet: `"tsp"`
120+
/// - Regtest: `"sprt"`
121+
/// - **Data**: version (1 byte) + scan_pubkey (33 bytes) + spend_pubkey (33 bytes)
122+
///
123+
/// # Example
124+
///
125+
/// ```ignore
126+
/// use secp256k1::PublicKey;
127+
/// use silentpayments::{SilentPaymentAddress, Network};
128+
///
129+
/// // After parsing bech32 yourself and extracting the pubkeys:
130+
/// let scan_pubkey = PublicKey::from_slice(&scan_bytes)?;
131+
/// let spend_pubkey = PublicKey::from_slice(&spend_bytes)?;
132+
///
133+
/// let address = SilentPaymentAddress::new(
134+
/// scan_pubkey,
135+
/// spend_pubkey,
136+
/// Network::Mainnet,
137+
/// 0 // version
138+
/// )?;
139+
/// ```
94140
pub fn new(
95141
scan_pubkey: PublicKey,
96142
m_pubkey: PublicKey,
@@ -111,25 +157,35 @@ impl SilentPaymentAddress {
111157
})
112158
}
113159

160+
/// Get the scan public key.
114161
pub fn get_scan_key(&self) -> PublicKey {
115162
self.scan_pubkey
116163
}
117164

165+
/// Get the spend public key.
118166
pub fn get_spend_key(&self) -> PublicKey {
119167
self.m_pubkey
120168
}
121169

170+
/// Get the network.
122171
pub fn get_network(&self) -> Network {
123172
self.network
124173
}
174+
175+
/// Get the version byte.
176+
pub fn get_version(&self) -> u8 {
177+
self.version
178+
}
125179
}
126180

181+
#[cfg(feature = "encode")]
127182
impl fmt::Display for SilentPaymentAddress {
128183
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129184
write!(f, "{}", <SilentPaymentAddress as Into<String>>::into(*self))
130185
}
131186
}
132187

188+
#[cfg(feature = "encode")]
133189
impl TryFrom<&str> for SilentPaymentAddress {
134190
type Error = Error;
135191

@@ -163,6 +219,7 @@ impl TryFrom<&str> for SilentPaymentAddress {
163219
}
164220
}
165221

222+
#[cfg(feature = "encode")]
166223
impl TryFrom<String> for SilentPaymentAddress {
167224
type Error = Error;
168225

@@ -171,6 +228,7 @@ impl TryFrom<String> for SilentPaymentAddress {
171228
}
172229
}
173230

231+
#[cfg(feature = "encode")]
174232
impl From<SilentPaymentAddress> for String {
175233
fn from(val: SilentPaymentAddress) -> Self {
176234
let hrp = match val.network {

0 commit comments

Comments
 (0)