Skip to content

Commit 217f2d3

Browse files
committed
test(encoding): add test vectors for encoding and helper methods
1 parent 4e0eba8 commit 217f2d3

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

  • silentpayments/src/encoding

silentpayments/src/encoding/mod.rs

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,295 @@ impl TryFrom<&str> for SilentPaymentCode {
159159
})
160160
}
161161
}
162+
163+
#[cfg(test)]
164+
mod test {
165+
use std::str::FromStr;
166+
167+
use bitcoin::{
168+
hex::DisplayHex,
169+
network::Network::Bitcoin,
170+
secp256k1::{PublicKey, Scalar, SecretKey},
171+
ScriptBuf,
172+
};
173+
174+
use once_cell::sync::Lazy;
175+
use serde::Deserialize;
176+
177+
use super::SilentPaymentCode;
178+
179+
const ENCODING_TEST_VECTORS: &str = r#"
180+
[
181+
{
182+
"index": 0,
183+
"comment": "successfully parse mainnet code",
184+
"input": "sp1qq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqewtzh728u7mzkne3uf0a35mzqlm0jf4q2kgc5aakq4d04a9l734ujpez3s",
185+
"error": null,
186+
"output": {
187+
"version": 0,
188+
"spend": "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
189+
"scan": "03f95241dfb00d1d42e2f48fb72e31a06b9fd166c1d6bd12648b41977dd51b9a0b",
190+
"network": "bitcoin"
191+
}
192+
},
193+
{
194+
"index": 1,
195+
"comment": "successfully parse testnet code",
196+
"input": "tsp1qq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqewtzh728u7mzkne3uf0a35mzqlm0jf4q2kgc5aakq4d04a9l734uxwehmt",
197+
"error": null,
198+
"output": {
199+
"version": 0,
200+
"spend": "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
201+
"scan": "03f95241dfb00d1d42e2f48fb72e31a06b9fd166c1d6bd12648b41977dd51b9a0b",
202+
"network": "testnet"
203+
}
204+
},
205+
{
206+
"index": 2,
207+
"comment": "successfully parse regtest code",
208+
"input": "sprt1qq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqewtzh728u7mzkne3uf0a35mzqlm0jf4q2kgc5aakq4d04a9l734u5ddn6e",
209+
"error": null,
210+
"output": {
211+
"version": 0,
212+
"spend": "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
213+
"scan": "03f95241dfb00d1d42e2f48fb72e31a06b9fd166c1d6bd12648b41977dd51b9a0b",
214+
"network": "regtest"
215+
}
216+
},
217+
{
218+
"index": 3,
219+
"comment": "fail to parse mainnet code with invalid spend key",
220+
"input": "sp19q0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqhpj0pezd9rdd9lvdcxz54gcwgph24j020xzu0nxvx6a0gr9u39ce35yz3n50",
221+
"error": "malformed public key",
222+
"output": null
223+
},
224+
{
225+
"index": 4,
226+
"comment": "fail to parse mainnet code with invalid scan key",
227+
"input": "sp19m66lz4dwzqcqe5vjndhawxr7x40cv8mjgxgzjuylmujqzhnelkgs7qle2fqalvqdr4pw9ay0kuhrrgrtnlgkdswkh5fxfz6pja7a2xu6pvv50l9w",
228+
"error": "malformed public key",
229+
"output": null
230+
},
231+
{
232+
"index": 5,
233+
"comment": "fail to parse code with wrong hrp",
234+
"input": "bc1qq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqle2fqalvqdr4pw9ay0kuhrrgrtnlgkdswkh5fxfz6pja7a2xu6pvgqultw",
235+
"error": "unknown hrp: bc",
236+
"output": null
237+
},
238+
{
239+
"index": 6,
240+
"comment": "successfully parse v5 code with data portion above 66 bytes",
241+
"input": "sp19q0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqewtzh728u7mzkne3uf0a35mzqlm0jf4q2kgc5aakq4d04a9l734ug7pexw6tsec0xextt0qextmudpaenmxyj688a48326cerg99n62kca3jutrxw3efjdytad3dyreupeugcdusgazwad388e6zfcu76056zzuz",
242+
"error": null,
243+
"output": {
244+
"version": 0,
245+
"spend": "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af",
246+
"scan": "03f95241dfb00d1d42e2f48fb72e31a06b9fd166c1d6bd12648b41977dd51b9a0b",
247+
"network": "bitcoin"
248+
}
249+
},
250+
{
251+
"index": 7,
252+
"comment": "fail to parse code with v31",
253+
"input": "sp1lq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqle2fqalvqdr4pw9ay0kuhrrgrtnlgkdswkh5fxfz6pja7a2xu6pvccpqt4",
254+
"error": "version 31 codes are not backward compatible",
255+
"output": null
256+
},
257+
{
258+
"index": 8,
259+
"comment": "fail to parse v0 mainnet code with invalid data size",
260+
"error": "payload length does not match version spec",
261+
"input": "sp1qq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqle2fqalvqdr4pw9ay0kuhrrgrtnlgkdswkh5fxfz6pja7a2xu6pdcn3286hvtm4jmfp67nnfxfk2ah8wy9t93u5dxs7qd56agnxuujkh27y86v3jlyzp65r47zumz4w4wje869xpaym6qzhxztcef7s4qfrzvyk2",
262+
"output": null
263+
},
264+
{
265+
"index": 9,
266+
"comment": "fail to parse v5 mainnet code with short data size",
267+
"error": "payload length does not match version spec",
268+
"input": "sp19q0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqknjxnvv",
269+
"output": null
270+
},
271+
{
272+
"index": 10,
273+
"comment": "fail to parse mainnet code with invalid checksum",
274+
"input": "sp1qq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkqewtzh728u7mzkne3uf0a35mzqlm0jf4q2kgc5aakq4d04a9l734ujptzes",
275+
"error": "invalid checksum",
276+
"output": null
277+
}
278+
]
279+
"#;
280+
281+
static ENCODING_TEST_CASES: Lazy<Vec<EncodingTestCase>> =
282+
Lazy::new(|| serde_json::from_str(ENCODING_TEST_VECTORS).expect("Invalid JSON"));
283+
284+
const SCAN_SK: &str = "57f0148f94d13095cfda539d0da0d1541304b678d8b36e243980aab4e1b7cead";
285+
const SCAN_PK: &str = "03f95241dfb00d1d42e2f48fb72e31a06b9fd166c1d6bd12648b41977dd51b9a0b";
286+
const SPEND_PK: &str = "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af";
287+
288+
#[derive(Debug, Deserialize)]
289+
struct EncodingTestCase {
290+
pub index: usize,
291+
#[serde(alias = "comment")]
292+
pub _comment: String,
293+
pub input: String,
294+
pub output: Option<TestOutput>,
295+
pub error: Option<String>,
296+
}
297+
298+
#[derive(Debug, Deserialize)]
299+
struct TestOutput {
300+
#[serde(alias = "version")]
301+
pub _version: u8,
302+
pub scan: String,
303+
pub spend: String,
304+
pub network: String,
305+
}
306+
307+
fn assert_encoding(test_index: usize) {
308+
let test_case = &ENCODING_TEST_CASES[test_index];
309+
assert_eq!(test_case.index, test_index);
310+
if test_case.error.is_some() && test_case.output.is_none() {
311+
let expected_error = test_case.error.clone().expect("already checked is some");
312+
let output = SilentPaymentCode::try_from(test_case.input.as_str());
313+
assert!(output.is_err());
314+
assert_eq!(expected_error, output.unwrap_err().to_string());
315+
} else if test_case.output.is_some() {
316+
let TestOutput {
317+
scan,
318+
spend,
319+
network,
320+
..
321+
} = test_case.output.as_ref().expect("already checked is some");
322+
let sp_code = SilentPaymentCode::try_from(test_case.input.as_str()).unwrap();
323+
assert_eq!(scan, &sp_code.scan.to_string());
324+
assert_eq!(spend, &sp_code.spend.to_string());
325+
assert_eq!(network, &sp_code.network.to_string());
326+
// Check roundtrip
327+
if sp_code.version() == 0 {
328+
assert_eq!(test_case.input, sp_code.to_string());
329+
}
330+
} else {
331+
panic!("test case definition is wrong");
332+
}
333+
}
334+
335+
fn scan_n_spend_pks() -> (PublicKey, PublicKey) {
336+
let scan = PublicKey::from_str(SCAN_PK).expect("reading from constant");
337+
let spend = PublicKey::from_str(SPEND_PK).expect("reading from constant");
338+
339+
(scan, spend)
340+
}
341+
342+
fn scan_sk() -> SecretKey {
343+
SecretKey::from_str(SCAN_SK).expect("reading from constant")
344+
}
345+
346+
#[test]
347+
fn test_0_successfully_parse_mainnet_code() {
348+
assert_encoding(0);
349+
}
350+
351+
#[test]
352+
fn test_1_successfully_parse_testnet_code() {
353+
assert_encoding(1);
354+
}
355+
356+
#[test]
357+
fn test_2_successfully_parse_regtest_code() {
358+
assert_encoding(2);
359+
}
360+
361+
#[test]
362+
fn test_3_fail_to_parse_mainnet_code_with_invalid_spend_key() {
363+
assert_encoding(3);
364+
}
365+
366+
#[test]
367+
fn test_4_fail_to_parse_mainnet_code_with_invalid_scan_key() {
368+
assert_encoding(4);
369+
}
370+
371+
#[test]
372+
fn test_5_fail_to_parse_code_with_wrong_hrp() {
373+
assert_encoding(5);
374+
}
375+
376+
#[test]
377+
fn test_6_successfully_parse_higher_version_code_with_data_portion_above_66_bytes() {
378+
assert_encoding(6);
379+
}
380+
381+
#[test]
382+
fn test_7_fail_to_parse_code_with_v31() {
383+
assert_encoding(7);
384+
}
385+
386+
#[test]
387+
fn test_8_fail_to_parse_v0_mainnet_code_with_invalid_data_size() {
388+
assert_encoding(8);
389+
}
390+
391+
#[test]
392+
fn fail_to_parse_v5_mainnet_code_with_short_data_size() {
393+
assert_encoding(9);
394+
}
395+
396+
#[test]
397+
fn fail_to_parse_mainnet_code_with_invalid_checksum() {
398+
assert_encoding(10);
399+
}
400+
401+
#[test]
402+
fn get_label() {
403+
let expected_label: &str =
404+
"6f3cec525b194328307cb10e83c559e29f946cd47e4a9a92eaf55967d9d22cfe";
405+
let output_label = SilentPaymentCode::get_label(scan_sk(), 4);
406+
assert_eq!(
407+
expected_label,
408+
output_label.to_be_bytes().as_hex().to_string()
409+
)
410+
}
411+
412+
#[test]
413+
fn get_labelled_sp_code() {
414+
let expected_labeled_code: &str = "sp1qq0u4yswlkqx36shz7j8mwt335p4el5txc8tt6yny3dqewlw4rwdqkq57x0y7k4rs5zkkd7pmumhkdadq7du5t7qf7nkyy6rfzp3jd697cg9zhz0x";
415+
let expected_labeled_spend_key: &str =
416+
"029e33c9eb5470a0ad66f83be6ef66f5a0f37945f809f4ec426869106326e8bec2";
417+
418+
let (scan, spend) = scan_n_spend_pks();
419+
let sp_code = SilentPaymentCode::new_v0(scan, spend, Bitcoin);
420+
let label = SilentPaymentCode::get_label(scan_sk(), 4);
421+
let output_labelled_code = sp_code.add_label(label).expect("should not err");
422+
assert_eq!(expected_labeled_code, output_labelled_code.to_string());
423+
assert_eq!(
424+
expected_labeled_spend_key,
425+
output_labelled_code.spend.to_string()
426+
);
427+
assert_eq!(SCAN_PK, output_labelled_code.scan.to_string())
428+
}
429+
430+
#[test]
431+
fn crafted_labeling_failure_case() {
432+
let (scan, spend) = scan_n_spend_pks();
433+
// Use the scan key as spend (because we only have the sk of scan)
434+
let sp_code = SilentPaymentCode::new_v0(spend, scan, Bitcoin);
435+
let negated_scan_scalar = Scalar::from(scan_sk().negate());
436+
let output = sp_code.add_label(negated_scan_scalar);
437+
assert!(output.is_err());
438+
assert_eq!("bad tweak", output.unwrap_err().to_string());
439+
}
440+
441+
#[test]
442+
fn check_placeholder_spk() {
443+
let (scan, spend) = scan_n_spend_pks();
444+
let expected_placeholder_spk: &str =
445+
"5120da3d55f0ecf27a3505ded1ac780c3d77299dc4b253010214ac9f2d1d9b365d09";
446+
let sp_code = SilentPaymentCode::new_v0(scan, spend, Bitcoin);
447+
let output_placeholder_spk: ScriptBuf = sp_code.get_placeholder_p2tr_spk();
448+
assert_eq!(
449+
expected_placeholder_spk,
450+
output_placeholder_spk.to_hex_string()
451+
);
452+
}
453+
}

0 commit comments

Comments
 (0)