Skip to content

Commit c2c1318

Browse files
committed
Add bip375 specific tests
1 parent d048a95 commit c2c1318

File tree

2 files changed

+258
-0
lines changed

2 files changed

+258
-0
lines changed

tests/bip375-parse-invalid.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//! BIP-375 Silent Payments Parse Invalid Tests
2+
3+
#![cfg(all(feature = "std", feature = "base64", feature = "silent-payments"))]
4+
5+
mod util;
6+
7+
use core::str::FromStr;
8+
9+
use psbt_v2::v2::{Creator, Psbt};
10+
11+
/// Test: Global field mismatch - DLEQ proofs present but no ECDH shares
12+
/// Expected error: DecodeError::FieldMismatch
13+
#[test]
14+
fn bip375_global_field_mismatch_dleq_only() {
15+
// Approach 1: Programmatic
16+
let mut psbt = Creator::new().psbt();
17+
psbt.global.sp_dleq_proofs.insert(vec![0x02u8; 33], vec![0xAAu8; 64]);
18+
19+
let bytes = psbt.serialize();
20+
assert!(Psbt::deserialize(&bytes).is_err(), "should fail due to DLEQ without ECDH");
21+
}
22+
23+
/// Test: Duplicate scan key in global ECDH shares
24+
/// Expected error: InsertPairError::DuplicateKey
25+
/// Note: This test demonstrates a limitation - BTreeMap prevents duplicates at construction,
26+
/// so we must use raw hex to test the deserialization error path.
27+
#[test]
28+
fn bip375_global_duplicate_scan_key_ecdh() {
29+
// Raw hex with duplicate ECDH entries for the same scan key
30+
// This will be caught during deserialization when inserting the second occurrence
31+
let hex = concat!(
32+
"70736274ff", // magic
33+
"01fb04",
34+
"02000000", // version = 2
35+
"010204",
36+
"02000000", // tx_version = 2
37+
"010401",
38+
"00", // input_count = 0
39+
"010501",
40+
"00", // output_count = 0
41+
"010601",
42+
"00", // tx_modifiable = 0
43+
// First ECDH entry with scan_key = 0x02 repeated 33 times
44+
"2207",
45+
"020202020202020202020202020202020202020202020202020202020202020202", // ECDH key (33 bytes)
46+
"21",
47+
"040404040404040404040404040404040404040404040404040404040404040404", // ECDH value (33 bytes)
48+
// Second ECDH entry with SAME scan_key but different value
49+
"2207",
50+
"020202020202020202020202020202020202020202020202020202020202020202", // ECDH key (33 bytes) - DUPLICATE!
51+
"21",
52+
"050505050505050505050505050505050505050505050505050505050505050505", // ECDH value (33 bytes, different)
53+
// Matching DLEQ entries (required to avoid field mismatch error)
54+
"2208",
55+
"020202020202020202020202020202020202020202020202020202020202020202", // DLEQ key (33 bytes)
56+
"40", // DLEQ value length (64 bytes)
57+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // DLEQ value part 1
58+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // DLEQ value part 2
59+
"00", // global terminator
60+
);
61+
62+
assert!(util::hex_psbt_v2(hex).is_err(), "should fail due to duplicate scan key in global");
63+
}
64+
65+
/// Test: Per-input field mismatch - DLEQ proofs present but no ECDH shares
66+
/// Expected error: DecodeError::FieldMismatch
67+
#[test]
68+
fn bip375_input_field_mismatch_dleq_only() {
69+
// Minimal PSBTv2 with one input containing DLEQ proof but no ECDH share
70+
let hex = concat!(
71+
"70736274ff", // magic
72+
"01fb04",
73+
"02000000", // version = 2
74+
"010204",
75+
"02000000", // tx_version = 2
76+
"010401",
77+
"01", // input_count = 1
78+
"010501",
79+
"00", // output_count = 0
80+
"010601",
81+
"00", // tx_modifiable = 0
82+
"00", // global terminator
83+
// Input 0
84+
"010e20",
85+
"0000000000000000000000000000000000000000000000000000000000000000", // prev txid
86+
"010f04",
87+
"00000000", // output index
88+
// DLEQ proof without matching ECDH share - MISMATCH!
89+
"221e",
90+
"020202020202020202020202020202020202020202020202020202020202020202", // DLEQ key (33 bytes)
91+
"40", // DLEQ value length (64 bytes)
92+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // DLEQ value part 1
93+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // DLEQ value part 2
94+
"00", // input terminator
95+
);
96+
97+
assert!(util::hex_psbt_v2(hex).is_err(), "should fail: input has DLEQ proof but no ECDH share");
98+
}
99+
100+
/// Test: Duplicate scan key in per-input ECDH shares
101+
/// Expected error: InsertPairError::DuplicateKey
102+
#[test]
103+
fn bip375_input_duplicate_scan_key_ecdh() {
104+
// Minimal PSBTv2 with one input containing duplicate ECDH shares for the same scan key
105+
let hex = concat!(
106+
"70736274ff", // magic
107+
"01fb04",
108+
"02000000", // version = 2
109+
"010204",
110+
"02000000", // tx_version = 2
111+
"010401",
112+
"01", // input_count = 1
113+
"010501",
114+
"00", // output_count = 0
115+
"010601",
116+
"00", // tx_modifiable = 0
117+
"00", // global terminator
118+
// Input 0
119+
"010e20",
120+
"0000000000000000000000000000000000000000000000000000000000000000", // prev txid
121+
"010f04",
122+
"00000000", // output index
123+
// First per-input ECDH entry with scan_key = 0x02 repeated 33 times
124+
"221d",
125+
"020202020202020202020202020202020202020202020202020202020202020202", // ECDH key (33 bytes)
126+
"21",
127+
"040404040404040404040404040404040404040404040404040404040404040404", // ECDH value (33 bytes)
128+
// Second per-input ECDH entry with SAME scan_key - DUPLICATE!
129+
"221d",
130+
"020202020202020202020202020202020202020202020202020202020202020202", // ECDH key (33 bytes) - DUPLICATE!
131+
"21",
132+
"050505050505050505050505050505050505050505050505050505050505050505", // ECDH value (33 bytes, different)
133+
// Matching DLEQ entries (required to avoid field mismatch error)
134+
"221e",
135+
"020202020202020202020202020202020202020202020202020202020202020202", // DLEQ key (33 bytes)
136+
"40", // DLEQ value length (64 bytes)
137+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // DLEQ value part 1
138+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // DLEQ value part 2
139+
"00", // input terminator
140+
);
141+
142+
assert!(util::hex_psbt_v2(hex).is_err(), "should fail due to duplicate scan key in input");
143+
}
144+
145+
// =============================================================================
146+
// BIP-375 Test Vectors - Invalid Cases Serialization Constraints
147+
// =============================================================================
148+
149+
/// Test: Missing DLEQ proof for ECDH share (per-input)
150+
/// Expected error: DecodeError::FieldMismatch
151+
#[test]
152+
fn bip375_test_vector_missing_input_dleq_proof() {
153+
let base64 = "cHNidP8B+wQCAAAAAQIEAgAAAAEEBAEAAAABBQQBAAAAAQYBAwABDiCrtYht20vGCALx8ZiisSkDZZzJ7nPgIx1FVehBiNyWQAEPBAAAAAABAR+ghgEAAAAAABYAFPja92rYA7DvqV1s/4ruCJHTrxyMARAE/v///yIGA9NX98BxjyR44/2PjMwnKd3YwMyusfArGBpvRNQ7n42NBAAAAAAiHQLQKf+W3iy894K+Q1nEhiDqkrzda+8DK5UVi5GhaT+0+CECVRZOeSbVDVKgn/mQZHpelcHbG/xophb7wtohOSf5i/8BAwQBAAAAAAEDCBhzAQAAAAAAAQQiUSAXu7qlEuAw+8o+5RT3sjgH4RGDGkdFRHCRNMn5v0a2xAEJQgLQKf+W3iy894K+Q1nEhiDqkrzda+8DK5UVi5GhaT+0+AJNUYNT9L0Y12nPaP9i7xBmm3CGJGsKZAP+V73kkhFEiwA=";
154+
155+
assert!(
156+
psbt_v2::v2::Psbt::from_str(base64).is_err(),
157+
"should fail: input has ECDH share but no DLEQ proof"
158+
);
159+
}
160+
161+
/// Test: Global ECDH share without DLEQ proof
162+
/// Expected error: DecodeError::FieldMismatch
163+
#[test]
164+
fn bip375_test_vector_global_ecdh_without_dleq() {
165+
let base64 = "cHNidP8B+wQCAAAAAQIEAgAAAAEEBAEAAAABBQQBAAAAAQYBAyIHAtAp/5beLLz3gr5DWcSGIOqSvN1r7wMrlRWLkaFpP7T4IQJVFk55JtUNUqCf+ZBkel6Vwdsb/GimFvvC2iE5J/mL/wABDiD2W3/BmfoPsrzNsfoGEte1D2K0+PqV1WXEoB9MWC6SpAEPBAAAAAABAR+ghgEAAAAAABYAFPja92rYA7DvqV1s/4ruCJHTrxyMARAE/v///yIGA9NX98BxjyR44/2PjMwnKd3YwMyusfArGBpvRNQ7n42NBAAAAAABAwQBAAAAAAEDCBhzAQAAAAAAAQQiUSCBAAI55HC7UjfZ+r6wGeJ5j8RXLnnJOQtdHV42G7fgIAEJQgLQKf+W3iy894K+Q1nEhiDqkrzda+8DK5UVi5GhaT+0+AJNUYNT9L0Y12nPaP9i7xBmm3CGJGsKZAP+V73kkhFEiwA=";
166+
167+
assert!(
168+
psbt_v2::v2::Psbt::from_str(base64).is_err(),
169+
"should fail: global ECDH share present but no DLEQ proof"
170+
);
171+
}

tests/bip375-parse-valid.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//! BIP-375 Silent Payments Parse Valid Tests
2+
3+
#![cfg(all(feature = "std", feature = "base64", feature = "silent-payments"))]
4+
5+
mod util;
6+
7+
use core::str::FromStr;
8+
9+
use psbt_v2::v2::{Creator, Psbt};
10+
11+
/// Helper: Create a minimal valid PSBTv2 with BIP-375 fields populated correctly
12+
fn valid_psbt_with_bip375_global_fields() -> Psbt {
13+
let mut psbt = Creator::new().psbt();
14+
let scan_key = vec![0x02u8; 33];
15+
let ecdh_share = vec![0x04u8; 33];
16+
let dleq_proof = vec![0xAAu8; 64];
17+
18+
psbt.global.sp_ecdh_shares.insert(scan_key.clone(), ecdh_share);
19+
psbt.global.sp_dleq_proofs.insert(scan_key, dleq_proof);
20+
psbt
21+
}
22+
23+
/// Test: Valid PSBT with both ECDH and DLEQ should succeed (sanity check)
24+
#[test]
25+
fn bip375_global_fields_both_present_valid() {
26+
let psbt = valid_psbt_with_bip375_global_fields();
27+
28+
let bytes = psbt.serialize();
29+
let result = Psbt::deserialize(&bytes);
30+
assert!(result.is_ok(), "should succeed when both ECDH and DLEQ are present");
31+
32+
let roundtrip = result.unwrap();
33+
assert_eq!(roundtrip.global.sp_ecdh_shares.len(), 1);
34+
assert_eq!(roundtrip.global.sp_dleq_proofs.len(), 1);
35+
}
36+
37+
// =============================================================================
38+
// BIP-375 Test Vectors - Valid Cases Serialization Constraints
39+
// =============================================================================
40+
41+
/// Test: Single signer with global ECDH share
42+
/// Source: BIP-375 test vectors
43+
#[test]
44+
fn bip375_test_vector_single_signer_global_shares_should_parse() {
45+
let base64 = "cHNidP8B+wQCAAAAAQIEAgAAAAEEBAEAAAABBQQBAAAAAQYBAyIHAtAp/5beLLz3gr5DWcSGIOqSvN1r7wMrlRWLkaFpP7T4IQJVFk55JtUNUqCf+ZBkel6Vwdsb/GimFvvC2iE5J/mL/yIIAtAp/5beLLz3gr5DWcSGIOqSvN1r7wMrlRWLkaFpP7T4QMHWfzh4gr+BeRXqFYIdZNWzg1wfwFxVjOPYJJKhfZCMEEh9u5vdwQgMjC2cGXGyZgg8aQVOIhk/JEEorGZzfmAAAQ4gbprLPJXWyu5NSnIlAmnrjz0Fcu1PhPS3/DOpmr4+OgUBDwQAAAAAAQEfoIYBAAAAAAAWABT42vdq2AOw76ldbP+K7giR068cjAEQBP7///8iBgPTV/fAcY8keOP9j4zMJynd2MDMrrHwKxgab0TUO5+NjQQAAAAAAQMEAQAAAAABAwgYcwEAAAAAAAEEIlEgrhn77icwoalS19JZjMcD/d87lyslFIse0aea6HOdXgcBCUIC0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPgCTVGDU/S9GNdpz2j/Yu8QZptwhiRrCmQD/le95JIRRIsA";
46+
47+
assert!(
48+
psbt_v2::v2::Psbt::from_str(base64).is_ok(),
49+
"should parse: single signer global shares"
50+
);
51+
}
52+
53+
/// Test: Multi-party with per-input ECDH shares
54+
/// Source: BIP-375 test vectors
55+
#[test]
56+
fn bip375_test_vector_per_input_shares_should_parse() {
57+
let base64 = "cHNidP8B+wQCAAAAAQIEAgAAAAEEBAIAAAABBQQBAAAAAQYBAwABDiBc+IxEpEzVImNhvtEHUZRvn4cJUikR4HeBX19/B4WvaQEPBAAAAAABAR9QwwAAAAAAABYAFPja92rYA7DvqV1s/4ruCJHTrxyMARAE/v///yIGA9NX98BxjyR44/2PjMwnKd3YwMyusfArGBpvRNQ7n42NBAAAAAABAwQBAAAAIh0C0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPghAlUWTnkm1Q1SoJ/5kGR6XpXB2xv8aKYW+8LaITkn+Yv/Ih4C0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPhAwdZ/OHiCv4F5FeoVgh1k1bODXB/AXFWM49gkkqF9kIwQSH27m93BCAyMLZwZcbJmCDxpBU4iGT8kQSisZnN+YAABDiAT0u662aEJkUdBDIam/gsg7p23WdpBz9Tul5LNUBI3FQEPBAAAAAABAR9QwwAAAAAAABYAFEIccVrt+YOvDjtnb/fElNEFT826ARAE/v///yIGAo8dCC1gAfpLqJmkA9r5sdsBvpJcIlM69jDuZJOwvp95BAAAAAABAwQBAAAAIh0C0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPghA1voNBApxyR/ork41dS4Wzpp+9kxe62Fr2V5knHctCgBIh4C0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPhAwP4szFLfu5Jb0/9lS3qW2OwOAxpn0EG0+Zyw6BFJ4oeoK9PVG8D/czrOfTKrY9YSGGVFd4CPssf7BtK+Bv86tgABAwgYcwEAAAAAAAEEIlEgC9xqHau4dRdnjCtffc3/KXbcXRE1xN2T9mcj6++5jiUBCUIC0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPgCTVGDU/S9GNdpz2j/Yu8QZptwhiRrCmQD/le95JIRRIsA";
58+
59+
assert!(
60+
psbt_v2::v2::Psbt::from_str(base64).is_ok(),
61+
"should parse: multi party per input shares"
62+
);
63+
}
64+
65+
/// Test: Silent payment with change detection
66+
/// Source: BIP-375 test vectors
67+
#[test]
68+
fn bip375_test_vector_output_with_change_should_parse() {
69+
let base64 = "cHNidP8B+wQCAAAAAQIEAgAAAAEEBAEAAAABBQQCAAAAAQYBAwABDiAlbK6m2hWAb7hW7a50mI1EDHqxtcCGHsgR0ZCSdHudHAEPBAAAAAABAR+ghgEAAAAAABYAFPja92rYA7DvqV1s/4ruCJHTrxyMARAE/v///yIGA9NX98BxjyR44/2PjMwnKd3YwMyusfArGBpvRNQ7n42NBAAAAAABAwQBAAAAIh0C0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPghAlUWTnkm1Q1SoJ/5kGR6XpXB2xv8aKYW+8LaITkn+Yv/Ih4C0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPhAwdZ/OHiCv4F5FeoVgh1k1bODXB/AXFWM49gkkqF9kIwQSH27m93BCAyMLZwZcbJmCDxpBU4iGT8kQSisZnN+YAABAwhQwwAAAAAAAAEEIlEgVbkWS8N9yG9biTYWgqowiLWz+lPa20PpXxvRE5uxwDUBCUIC0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPgD9SRDSFIBZWa8RdH6alxaGGLN2TPxXIQ7QnI4IvUlMjcBCgQBAAAAAAEDCMivAAAAAAAAAQQWABTjwxDMKvOsbmLK5L0j4+5SueHJWSICA9NX98BxjyR44/2PjMwnKd3YwMyusfArGBpvRNQ7n42NDAAAAAAAAAAAAAAAAQA=";
70+
71+
assert!(
72+
psbt_v2::v2::Psbt::from_str(base64).is_ok(),
73+
"should parse: silent payment with change detection"
74+
);
75+
}
76+
77+
/// Test: Multiple silent payment outputs to same scan key
78+
/// Source: BIP-375 test vectors
79+
#[test]
80+
fn bip375_test_vector_multiple_outputs_same_key_should_parse() {
81+
let base64 = "cHNidP8B+wQCAAAAAQIEAgAAAAEEBAEAAAABBQQCAAAAAQYBAyIHAtAp/5beLLz3gr5DWcSGIOqSvN1r7wMrlRWLkaFpP7T4IQJVFk55JtUNUqCf+ZBkel6Vwdsb/GimFvvC2iE5J/mL/yIIAtAp/5beLLz3gr5DWcSGIOqSvN1r7wMrlRWLkaFpP7T4QMHWfzh4gr+BeRXqFYIdZNWzg1wfwFxVjOPYJJKhfZCMEEh9u5vdwQgMjC2cGXGyZgg8aQVOIhk/JEEorGZzfmAAAQ4gLHvTL/FQccCuAyc4ZKFDbIpWITVp4RMtz46nPsjDMiIBDwQAAAAAAQEfoIYBAAAAAAAWABT42vdq2AOw76ldbP+K7giR068cjAEQBP7///8iBgPTV/fAcY8keOP9j4zMJynd2MDMrrHwKxgab0TUO5+NjQQAAAAAAQMEAQAAAAABAwhAnAAAAAAAAAEEIlEg+ytxOv1SuiRxgbmYQapPLIhVut99rOLrLjBV2hPuK4wBCUIC0Cn/lt4svPeCvkNZxIYg6pK83WvvAyuVFYuRoWk/tPgCTVGDU/S9GNdpz2j/Yu8QZptwhiRrCmQD/le95JIRRIsAAQMI2NYAAAAAAAABBCJRIFmgqeG9mJh0IBNVOGDtC0S3JvvFIOnFcbxGSV8kefYYAQlCAtAp/5beLLz3gr5DWcSGIOqSvN1r7wMrlRWLkaFpP7T4Ak1Rg1P0vRjXac9o/2LvEGabcIYkawpkA/5XveSSEUSLAA==";
82+
83+
assert!(
84+
psbt_v2::v2::Psbt::from_str(base64).is_ok(),
85+
"should parse: multiple outputs to same scan key"
86+
);
87+
}

0 commit comments

Comments
 (0)