Skip to content

Commit 8efb0a6

Browse files
committed
feat(silentpayments): add sending support
1 parent 1889342 commit 8efb0a6

File tree

7 files changed

+206
-0
lines changed

7 files changed

+206
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ alloc = ["secp256k1-sys/alloc"]
2121
recovery = ["secp256k1-sys/recovery"]
2222
lowmemory = ["secp256k1-sys/lowmemory"]
2323
global-context = ["std"]
24+
silentpayments = ["secp256k1-sys/silentpayments"]
2425
# disable re-randomization of the global context, which provides some
2526
# defense-in-depth against sidechannel attacks. You should only use
2627
# this feature if you expect the `rand` crate's thread_rng to panic.

secp256k1-sys/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ recovery = []
3232
lowmemory = []
3333
std = ["alloc"]
3434
alloc = []
35+
silentpayments = []
3536

3637
[lints.rust]
3738
unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(secp256k1_fuzz)', 'cfg(rust_secp_no_symbol_renaming)'] }

secp256k1-sys/build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ fn main() {
3232
// just #define it away.
3333
.define("printf(...)", Some(""));
3434

35+
if cfg!(feature = "silentpayments") {
36+
base_config.define("ENABLE_MODULE_SILENTPAYMENTS", Some("1"));
37+
}
38+
3539
if cfg!(feature = "lowmemory") {
3640
base_config.define("ECMULT_WINDOW_SIZE", Some("4")); // A low-enough value to consume negligible memory
3741
base_config.define("COMB_BLOCKS", Some("2"));

secp256k1-sys/src/lib.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,43 @@ extern "C" {
919919
) -> c_int;
920920
}
921921

922+
#[cfg(feature = "silentpayments")]
923+
pub mod silentpayments {
924+
use super::*;
925+
926+
extern "C" {
927+
#[cfg_attr(
928+
not(rust_secp_no_symbol_renaming),
929+
link_name = "rustsecp256k1_v0_13_silentpayments_sender_create_outputs"
930+
)]
931+
pub fn secp256k1_silentpayments_sender_create_outputs(
932+
ctx: *const Context,
933+
generated_outputs: *mut *mut XOnlyPublicKey,
934+
recipients: *const *mut Recipient,
935+
n_recipients: size_t,
936+
outpoint_smallest36: *const c_uchar,
937+
taproot_seckeys: *const *const Keypair,
938+
n_taproot_seckeys: size_t,
939+
plain_seckeys: *const *const c_uchar,
940+
n_plain_seckeys: size_t,
941+
) -> c_int;
942+
}
943+
944+
#[repr(C)]
945+
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
946+
pub struct Recipient {
947+
scan_pubkey: PublicKey,
948+
spend_pubkey: PublicKey,
949+
index: size_t,
950+
}
951+
952+
impl Recipient {
953+
pub fn new(scan_pubkey: &PublicKey, spend_pubkey: &PublicKey, index: usize) -> Self {
954+
Self { scan_pubkey: *scan_pubkey, spend_pubkey: *spend_pubkey, index }
955+
}
956+
}
957+
}
958+
922959
#[cfg(not(secp256k1_fuzz))]
923960
extern "C" {
924961
// Contexts

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ pub mod ellswift;
179179
pub mod musig;
180180
pub mod scalar;
181181
pub mod schnorr;
182+
#[cfg(feature = "silentpayments")]
183+
pub mod silentpayments;
182184

183185
use core::marker::PhantomData;
184186
use core::ptr::NonNull;

src/silentpayments/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod sender;

src/silentpayments/sender.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//! Provide all the binding functions and methods to be used from the perspective of the
2+
//! silentpayments sender.
3+
use crate::ffi::{self, CPtr};
4+
use crate::{Keypair, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey};
5+
6+
/// Struct to store recipient data.
7+
#[repr(transparent)]
8+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
9+
pub struct Recipient(ffi::silentpayments::Recipient);
10+
11+
impl Recipient {
12+
/// Get a new [`Recipient`]
13+
pub fn new(scan_pubkey: &PublicKey, spend_pubkey: &PublicKey, index: usize) -> Self {
14+
unsafe {
15+
Self(ffi::silentpayments::Recipient::new(
16+
&*scan_pubkey.as_c_ptr(),
17+
&*spend_pubkey.as_c_ptr(),
18+
index,
19+
))
20+
}
21+
}
22+
}
23+
24+
/// Error creating silent payment ouput x-only public keys.
25+
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
26+
pub struct DerivationError;
27+
28+
#[cfg(feature = "std")]
29+
impl std::error::Error for DerivationError {}
30+
31+
impl core::fmt::Display for DerivationError {
32+
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
33+
match self {
34+
DerivationError => {
35+
write!(f, "Failed while deriving silent payment output x-only public keys")
36+
}
37+
}
38+
}
39+
}
40+
41+
/// Create Silent Payment outputs for recipient(s).
42+
///
43+
/// Given a list of n secret keys a_1...a_n (one for each silent payment
44+
/// eligible input to spend), a serialized outpoint, and a list of recipients,
45+
/// create the taproot outputs. Inputs with conditional branches or multiple
46+
/// public keys are excluded from silent payments eligible inputs; see BIP352
47+
/// for more information.
48+
///
49+
/// `lexmin_outpoint` refers to the smallest outpoint lexicographically
50+
/// from the transaction inputs (both silent payments eligible and non-eligible
51+
/// inputs). This value MUST be the smallest outpoint out of ALL of the
52+
/// transaction inputs, otherwise the recipient will be unable to find the
53+
/// payment. Determining the smallest outpoint from the list of transaction
54+
/// inputs is the responsibility of the caller. It is strongly recommended
55+
/// that implementations ensure they are doing this correctly by using the
56+
/// test vectors from BIP352.
57+
///
58+
/// When creating more than one generated output, all of the generated outputs
59+
/// MUST be included in the final transaction. Dropping any of the generated
60+
/// outputs from the final transaction may make all or some of the outputs
61+
/// unfindable by the recipient.
62+
///
63+
/// # Arguments
64+
/// * `recipients` - slice of [`Recipient`] mutable references. The index indicates
65+
/// its position in the original ordering. The recipients will be grouped by scan public key in
66+
/// place (as specified in BIP0352), but generated outputs are saved in the `generated_outputs`
67+
/// array to match the original ordering (using the index field). This ensures the caller is able
68+
/// to match the generated outputs to the correct silent payment addresses. The same recipient can
69+
/// be passed multiple times to create multiple outputs for the same recipient.
70+
/// * `lexmin_outpoint` - serialized (36-byte) smallest outpoint (lexicographically) from the transaction inputs
71+
/// * `taproot_seckeys` - optionally a slice of [`Keypair`] references of taproot inputs.
72+
/// * `plain_seckeys` - optionally a slice of [`SecretKey`] references of non-taproot inputs.
73+
///
74+
/// # Returns
75+
/// A vector to xonly public keys, one per recipient. Outputs are ordered to match the original
76+
/// ordering of the recipient objects, i.e., the vector element zero is the generated output for
77+
/// the [`Recipient`] struct with index = 0.
78+
///
79+
/// # Errors
80+
/// * [`SilentpaymentDerivationError`] - This is expected only with an adversarially chosen
81+
/// recipient spend key. Specifically, failure occurs when:
82+
/// - Input secret keys sum to 0 or the negation of a spend key (negligible probability if at least
83+
/// one of the input secret keys is uniformly random and independent of all other keys).
84+
/// - A hash output is not a valid scalar (negligible probability per hash evaluation).
85+
pub fn create_outputs(
86+
recipients: &[&mut Recipient],
87+
lexmin_outpoint: &[u8; 36],
88+
taproot_seckeys: Option<&[&Keypair]>,
89+
plain_seckeys: Option<&[&SecretKey]>,
90+
) -> Result<Vec<XOnlyPublicKey>, DerivationError> {
91+
let mut seed = [0u8; 32];
92+
93+
let (ffi_taproot_seckeys, n_taproot_seckeys) = match taproot_seckeys {
94+
Some(keys) => {
95+
for key in keys {
96+
for (this, that) in seed.iter_mut().zip(key.to_secret_bytes().iter()) {
97+
*this ^= *that;
98+
}
99+
}
100+
(keys.as_c_ptr() as *const *const ffi::Keypair, keys.len())
101+
}
102+
None =>
103+
(core::ptr::null::<*const *const ffi::Keypair>() as *const *const ffi::Keypair, 0_usize),
104+
};
105+
106+
let (ffi_plain_seckeys, n_plain_seckeys) = match plain_seckeys {
107+
Some(keys) => {
108+
for key in keys {
109+
for (this, that) in seed.iter_mut().zip(key.to_secret_bytes().iter()) {
110+
*this ^= *that;
111+
}
112+
}
113+
(
114+
keys.iter()
115+
.map(|key| key.to_secret_bytes().as_c_ptr())
116+
.collect::<Vec<*const u8>>()
117+
.as_c_ptr(),
118+
keys.len(),
119+
)
120+
}
121+
None => (core::ptr::null::<*const *const u8>() as *const *const u8, 0_usize),
122+
};
123+
124+
let mut ffi_generated_outputs = unsafe { vec![ffi::XOnlyPublicKey::new(); recipients.len()] };
125+
let mut ffi_generated_outputs_refs =
126+
ffi_generated_outputs.iter_mut().map(|k| k as *mut _).collect::<Vec<_>>();
127+
128+
let res = crate::with_global_context(
129+
|secp: &Secp256k1<crate::AllPreallocated>| unsafe {
130+
ffi::silentpayments::secp256k1_silentpayments_sender_create_outputs(
131+
secp.ctx().as_ptr(),
132+
ffi_generated_outputs_refs.as_mut_c_ptr(),
133+
recipients.as_c_ptr() as *const *mut ffi::silentpayments::Recipient,
134+
recipients.len(),
135+
lexmin_outpoint.as_c_ptr(),
136+
ffi_taproot_seckeys,
137+
n_taproot_seckeys,
138+
ffi_plain_seckeys,
139+
n_plain_seckeys,
140+
)
141+
},
142+
Some(&seed),
143+
);
144+
145+
if res == 1 {
146+
let generated_outputs = {
147+
let mut generated_outputs_drop = core::mem::ManuallyDrop::new(ffi_generated_outputs);
148+
let (ptr, len, cap) = (
149+
generated_outputs_drop.as_mut_c_ptr(),
150+
generated_outputs_drop.len(),
151+
generated_outputs_drop.capacity(),
152+
);
153+
// Transfer control of memory to new vector
154+
unsafe { Vec::from_raw_parts(ptr as *mut XOnlyPublicKey, len, cap) }
155+
};
156+
Ok(generated_outputs)
157+
} else {
158+
Err(DerivationError)
159+
}
160+
}

0 commit comments

Comments
 (0)