Skip to content

Commit 776653d

Browse files
committed
doc(silentpayments): add example/silentpayments.rs
1 parent 18e176b commit 776653d

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ required-features = ["rand", "std"]
7272
name = "musig"
7373
required-features = ["rand", "std"]
7474

75+
[[example]]
76+
name = "silentpayments"
77+
required-features = ["rand", "silentpayments"]
78+
7579
[workspace]
7680
members = ["secp256k1-sys"]
7781
exclude = ["no_std_test"]

examples/silentpayments.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
use secp256k1::{
2+
silentpayments, Keypair, PublicKey, Scalar, SecretKey, XOnlyPublicKey,
3+
};
4+
use std::collections::HashMap;
5+
6+
const N_INPUTS: usize = 2;
7+
const N_OUTPUTS: usize = 3;
8+
9+
/* Static data for Bob and Carol's silent payment addresses */
10+
static SMALLEST_OUTPOINT: [u8; 36] = [
11+
0x16, 0x9e, 0x1e, 0x83, 0xe9, 0x30, 0x85, 0x33, 0x91,
12+
0xbc, 0x6f, 0x35, 0xf6, 0x05, 0xc6, 0x75, 0x4c, 0xfe,
13+
0xad, 0x57, 0xcf, 0x83, 0x87, 0x63, 0x9d, 0x3b, 0x40,
14+
0x96, 0xc5, 0x4f, 0x18, 0xf4, 0x00, 0x00, 0x00, 0x00,
15+
];
16+
17+
static BOB_SCAN_KEY: [u8; 32] = [
18+
0xa8, 0x90, 0x54, 0xc9, 0x5b, 0xe3, 0xc3, 0x01,
19+
0x56, 0x65, 0x74, 0xf2, 0xaa, 0x93, 0xad, 0xe0,
20+
0x51, 0x85, 0x09, 0x03, 0xa6, 0x9c, 0xbd, 0xd1,
21+
0xd4, 0x7e, 0xae, 0x26, 0x3d, 0x7b, 0xc0, 0x31,
22+
];
23+
24+
static BOB_SPEND_KEY: [u8; 32] = [
25+
0x9d, 0x6a, 0xd8, 0x55, 0xce, 0x34, 0x17, 0xef,
26+
0x84, 0xe8, 0x36, 0x89, 0x2e, 0x5a, 0x56, 0x39,
27+
0x2b, 0xfb, 0xa0, 0x5f, 0xa5, 0xd9, 0x7c, 0xce,
28+
0xa3, 0x0e, 0x26, 0x6f, 0x54, 0x0e, 0x08, 0xb3,
29+
];
30+
31+
static BOB_SCAN_AND_SPEND_PUBKEYS: [[u8; 33]; 2] = [
32+
[
33+
0x02, 0x15, 0x40, 0xae, 0xa8, 0x97, 0x54, 0x7a,
34+
0xd4, 0x39, 0xb4, 0xe0, 0xf6, 0x09, 0xe5, 0xf0,
35+
0xfa, 0x63, 0xde, 0x89, 0xab, 0x11, 0xed, 0xe3,
36+
0x1e, 0x8c, 0xde, 0x4b, 0xe2, 0x19, 0x42, 0x5f, 0x23,
37+
],
38+
[
39+
0x02, 0x5c, 0xc9, 0x85, 0x6d, 0x6f, 0x83, 0x75,
40+
0x35, 0x0e, 0x12, 0x39, 0x78, 0xda, 0xac, 0x20,
41+
0x0c, 0x26, 0x0c, 0xb5, 0xb5, 0xae, 0x83, 0x10,
42+
0x6c, 0xab, 0x90, 0x48, 0x4d, 0xcd, 0x8f, 0xcf, 0x36,
43+
],
44+
];
45+
46+
static CAROL_SCAN_KEY: [u8; 32] = [
47+
0x04, 0xb2, 0xa4, 0x11, 0x63, 0x5c, 0x09, 0x77,
48+
0x59, 0xaa, 0xcd, 0x0f, 0x00, 0x5a, 0x4c, 0x82,
49+
0xc8, 0xc9, 0x28, 0x62, 0xc6, 0xfc, 0x28, 0x4b,
50+
0x80, 0xb8, 0xef, 0xeb, 0xc2, 0x0c, 0x3d, 0x17,
51+
];
52+
53+
static CAROL_ADDRESS: [[u8; 33]; 2] = [
54+
[
55+
0x03, 0xbb, 0xc6, 0x3f, 0x12, 0x74, 0x5d, 0x3b,
56+
0x9e, 0x9d, 0x24, 0xc6, 0xcd, 0x7a, 0x1e, 0xfe,
57+
0xba, 0xd0, 0xa7, 0xf4, 0x69, 0x23, 0x2f, 0xbe,
58+
0xcf, 0x31, 0xfb, 0xa7, 0xb4, 0xf7, 0xdd, 0xed, 0xa8,
59+
],
60+
[
61+
0x03, 0x81, 0xeb, 0x9a, 0x9a, 0x9e, 0xc7, 0x39,
62+
0xd5, 0x27, 0xc1, 0x63, 0x1b, 0x31, 0xb4, 0x21,
63+
0x56, 0x6f, 0x5c, 0x2a, 0x47, 0xb4, 0xab, 0x5b,
64+
0x1f, 0x6a, 0x68, 0x6d, 0xfb, 0x68, 0xea, 0xb7, 0x16,
65+
],
66+
];
67+
68+
fn main() {
69+
let mut sender_keypairs = Vec::<Keypair>::new();
70+
let mut recipients = Vec::<silentpayments::sender::Recipient>::new();
71+
72+
let unlabeled_spend_pubkey =
73+
PublicKey::from_byte_array_compressed(BOB_SCAN_AND_SPEND_PUBKEYS[1])
74+
.expect("reading from constant, should not fail");
75+
76+
let (bob_address, label_context) = {
77+
let bob_scan_key = SecretKey::from_secret_bytes(BOB_SCAN_KEY)
78+
.expect("reading from constant, should not fail");
79+
80+
let m = 1;
81+
let (label, label_tweak) = silentpayments::recipient::create_label(&bob_scan_key, m)
82+
.expect("transitively deterministic, should not fail");
83+
84+
let mut tweak_map = HashMap::<[u8; 33], [u8; 32]>::new();
85+
86+
tweak_map.insert(label.serialize(), label_tweak);
87+
88+
let labeled_spend_pubkey =
89+
silentpayments::recipient::create_labeled_spend_pubkey(&unlabeled_spend_pubkey, &label)
90+
.expect("transitively deterministic, should not fail");
91+
92+
let bob_address: [[u8; 33]; 2] =
93+
[BOB_SCAN_AND_SPEND_PUBKEYS[0], labeled_spend_pubkey.serialize()];
94+
95+
(bob_address, tweak_map)
96+
};
97+
98+
let (tx_inputs, tx_outputs) = {
99+
let mut tx_inputs = Vec::<XOnlyPublicKey>::new();
100+
101+
for _ in 0..N_INPUTS {
102+
let rand_keypair = Keypair::new(&mut rand::rng());
103+
sender_keypairs.push(rand_keypair);
104+
tx_inputs.push(rand_keypair.x_only_public_key().0);
105+
}
106+
107+
let sp_addresses = [&CAROL_ADDRESS, &bob_address, &CAROL_ADDRESS];
108+
109+
for (index, address) in sp_addresses.iter().enumerate() {
110+
let scan_pubkey = PublicKey::from_byte_array_compressed(address[0])
111+
.expect("reading from constant, should not fail");
112+
let spend_pubkey = PublicKey::from_byte_array_compressed(address[1])
113+
.expect("reading from constant, should not fail");
114+
115+
let silentpayment_recipient =
116+
silentpayments::sender::Recipient::new(&scan_pubkey, &spend_pubkey, index);
117+
118+
recipients.push(silentpayment_recipient);
119+
}
120+
121+
let recipients: Vec<&mut _> = recipients.iter_mut().collect();
122+
let sender_keypairs: Vec<&_> = sender_keypairs.iter().collect();
123+
124+
let tx_outputs = silentpayments::sender::create_outputs(
125+
&recipients,
126+
&SMALLEST_OUTPOINT,
127+
Some(&sender_keypairs),
128+
None,
129+
)
130+
.expect("negligible probability of error, should not fail");
131+
132+
assert_eq!(tx_outputs.len(), N_OUTPUTS);
133+
134+
println!("Alice created the following outputs for Bob and Carol:");
135+
for tx_output in tx_outputs.iter() {
136+
println!("\t0x{}", &tx_output.to_string());
137+
}
138+
println!();
139+
140+
(tx_inputs, tx_outputs)
141+
};
142+
143+
let tx_inputs_ref: Vec<&XOnlyPublicKey> = tx_inputs.iter().collect();
144+
let tx_outputs_ref: Vec<&XOnlyPublicKey> = tx_outputs.iter().collect();
145+
146+
let prevouts_summary = silentpayments::recipient::PrevoutsSummary::create(
147+
&SMALLEST_OUTPOINT,
148+
Some(&tx_inputs_ref),
149+
None,
150+
)
151+
.expect("all arguments are valid and and all inputs are xonly inputs, should not fail");
152+
153+
let bob_scan_key =
154+
SecretKey::from_secret_bytes(BOB_SCAN_KEY).expect("reading from constant, should not fail");
155+
156+
let label_lookup = |key: &[u8; 33]| -> Option<[u8; 32]> { label_context.get(key).copied() };
157+
let found_outputs = silentpayments::recipient::scan_outputs(
158+
&tx_outputs_ref,
159+
&bob_scan_key,
160+
&prevouts_summary,
161+
&unlabeled_spend_pubkey,
162+
Some(&label_lookup),
163+
)
164+
.expect("all arguments are valid, should not fail");
165+
166+
if !found_outputs.is_empty() {
167+
println!("Bob found the following outputs:");
168+
for xonly_output in found_outputs {
169+
println!("\t0x{}", &xonly_output.to_string());
170+
let bob_spend_key = SecretKey::from_secret_bytes(BOB_SPEND_KEY)
171+
.expect("reading from constant, should not fail");
172+
let bob_tweaked_key = bob_spend_key
173+
.add_tweak(
174+
&Scalar::from_be_bytes(xonly_output.tweak())
175+
.expect("generated by sender, should be less than curve"),
176+
)
177+
.expect("negligible probability of error, should not fail");
178+
let bob_spend_keypair = Keypair::from_secret_key(&bob_tweaked_key);
179+
let (bob_tweaked_xonly_pubkey, _parity) = bob_spend_keypair.x_only_public_key();
180+
assert_eq!(xonly_output.output(), bob_tweaked_xonly_pubkey);
181+
}
182+
println!();
183+
} else {
184+
println!("Bob did not find any outputs in this transaction.\n");
185+
}
186+
187+
let unlabeled_spend_pubkey = PublicKey::from_byte_array_compressed(CAROL_ADDRESS[1])
188+
.expect("reading from constant, should not fail");
189+
190+
let carol_scan_key = SecretKey::from_secret_bytes(CAROL_SCAN_KEY)
191+
.expect("reading from constant, should not fail");
192+
193+
let found_outputs = silentpayments::recipient::scan_outputs(
194+
&tx_outputs_ref,
195+
&carol_scan_key,
196+
&prevouts_summary,
197+
&unlabeled_spend_pubkey,
198+
None::<fn(&[u8; 33]) -> Option<[u8; 32]>>,
199+
)
200+
.expect("arguments are valid and tx outputs are silent payment outputs, should not fail");
201+
202+
if !found_outputs.is_empty() {
203+
println!("Carol found the following outputs:");
204+
for xonly_output in found_outputs {
205+
println!("\t0x{}", &xonly_output.to_string());
206+
}
207+
} else {
208+
println!("Carol did not find any outputs in this transaction.\n");
209+
}
210+
}

0 commit comments

Comments
 (0)