Skip to content

Commit 3318edd

Browse files
committed
Merge pull request #18 from 'test/add-unit-test-for-get-smallest-outpoint'
test(lib): add unit test for get_smallest_lexicographic_outpoint perf(lib): replace N allocations by 1 in get_smallest_lexicographic_outpoint
2 parents 53997c3 + 24fcdbf commit 3318edd

1 file changed

Lines changed: 181 additions & 8 deletions

File tree

silentpayments/src/lib.rs

Lines changed: 181 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,33 @@ pub fn tag_txin(txin: &TxIn, script_pubkey: &ScriptBuf) -> Option<SpInputs> {
6565
}
6666

6767
pub fn get_smallest_lexicographic_outpoint(outpoints: &[OutPoint]) -> [u8; 36] {
68-
outpoints
68+
// Find the outpoint with the smallest lexicographic order
69+
let smallest = outpoints
6970
.iter()
70-
.map(|x| {
71-
let mut outpoint_bytes = [0u8; 36];
72-
outpoint_bytes[..32].copy_from_slice(x.txid.to_raw_hash().as_byte_array());
73-
outpoint_bytes[32..36].copy_from_slice(&x.vout.to_le_bytes());
74-
outpoint_bytes
71+
.min_by(|a, b| {
72+
// Compare txids first
73+
let a_txid = a.txid.to_raw_hash();
74+
let b_txid = b.txid.to_raw_hash();
75+
76+
// If txids are different, compare them
77+
match a_txid.as_byte_array().cmp(b_txid.as_byte_array()) {
78+
std::cmp::Ordering::Equal => {
79+
// If txids are equal, compare vouts directly
80+
let a_vout_bytes = a.vout.to_le_bytes();
81+
let b_vout_bytes = b.vout.to_le_bytes();
82+
a_vout_bytes.cmp(&b_vout_bytes)
83+
}
84+
other => other,
85+
}
7586
})
76-
.min()
77-
.expect("cannot create silent payment script pubkey without outpoints")
87+
.expect("cannot create silent payment script pubkey without outpoints");
88+
89+
// Only allocate the result array once we have the smallest outpoint
90+
let mut result = [0u8; 36];
91+
result[..32].copy_from_slice(smallest.txid.to_raw_hash().as_byte_array());
92+
result[32..36].copy_from_slice(&smallest.vout.to_le_bytes());
93+
94+
result
7895
}
7996

8097
pub fn compute_shared_secret(sk: &SecretKey, pk: &PublicKey) -> PublicKey {
@@ -87,3 +104,159 @@ pub fn compute_shared_secret(sk: &SecretKey, pk: &PublicKey) -> PublicKey {
87104

88105
PublicKey::from_slice(&ss_bytes).expect("computationally unreachable: can only fail if public key is invalid in the first place or sk is")
89106
}
107+
108+
#[cfg(test)]
109+
#[cfg_attr(coverage_nightly, coverage(off))]
110+
mod tests {
111+
use super::*;
112+
use bitcoin::OutPoint;
113+
use bitcoin::Txid;
114+
115+
#[test]
116+
fn test_get_smallest_outpoint_different_txids_and_vouts() {
117+
let outpoints = vec![
118+
OutPoint {
119+
txid: Txid::from_slice(&[3u8; 32]).unwrap(),
120+
vout: 2,
121+
},
122+
OutPoint {
123+
txid: Txid::from_slice(&[2u8; 32]).unwrap(),
124+
vout: 1,
125+
},
126+
OutPoint {
127+
txid: Txid::from_slice(&[5u8; 32]).unwrap(),
128+
vout: 3,
129+
},
130+
];
131+
132+
let result = get_smallest_lexicographic_outpoint(&outpoints);
133+
134+
let mut expected_bytes = [2u8; 36];
135+
expected_bytes[32..36].copy_from_slice(&1u32.to_le_bytes());
136+
137+
assert_eq!(result, expected_bytes);
138+
}
139+
140+
#[test]
141+
#[should_panic(expected = "cannot create silent payment script pubkey without outpoints")]
142+
fn test_get_smallest_outpoint_empty() {
143+
let outpoints: Vec<OutPoint> = vec![];
144+
get_smallest_lexicographic_outpoint(&outpoints);
145+
}
146+
147+
// Additional test: same txid, different vouts
148+
#[test]
149+
fn test_get_smallest_outpoint_identical_txid_different_vouts() {
150+
let txid = Txid::from_slice(&[0u8; 32]).unwrap();
151+
let outpoints = vec![
152+
OutPoint { txid, vout: 10 },
153+
OutPoint { txid, vout: 2 },
154+
OutPoint { txid, vout: 5 },
155+
];
156+
157+
let result = get_smallest_lexicographic_outpoint(&outpoints);
158+
159+
let mut expected_bytes = [0u8; 36];
160+
expected_bytes[32..36].copy_from_slice(&2u32.to_le_bytes());
161+
assert_eq!(result, expected_bytes);
162+
}
163+
164+
#[test]
165+
fn test_get_smallest_outpoint_same_vout_different_txid() {
166+
let outpoints = vec![
167+
OutPoint {
168+
txid: Txid::from_slice(&[2u8; 32]).unwrap(),
169+
vout: 7,
170+
},
171+
OutPoint {
172+
txid: Txid::from_slice(&[1u8; 32]).unwrap(),
173+
vout: 7,
174+
},
175+
OutPoint {
176+
txid: Txid::from_slice(&[3u8; 32]).unwrap(),
177+
vout: 7,
178+
},
179+
];
180+
181+
let result = get_smallest_lexicographic_outpoint(&outpoints);
182+
183+
let mut expected_bytes = [1u8; 36];
184+
expected_bytes[32..36].copy_from_slice(&7u32.to_le_bytes());
185+
assert_eq!(result, expected_bytes);
186+
}
187+
188+
#[test]
189+
fn test_get_smallest_outpoint_edge_case_max_vout() {
190+
let outpoints = vec![
191+
OutPoint {
192+
txid: Txid::from_slice(&[1u8; 32]).unwrap(),
193+
vout: u32::MAX,
194+
},
195+
OutPoint {
196+
txid: Txid::from_slice(&[1u8; 32]).unwrap(),
197+
vout: u32::MIN,
198+
},
199+
];
200+
201+
let result = get_smallest_lexicographic_outpoint(&outpoints);
202+
203+
let mut expected_bytes = [1u8; 36];
204+
expected_bytes[..32].copy_from_slice(&[1u8; 32]);
205+
expected_bytes[32..36].copy_from_slice(&0u32.to_le_bytes());
206+
assert_eq!(result, expected_bytes);
207+
}
208+
209+
#[test]
210+
fn test_get_smallest_outpoint_txid_takes_precedence() {
211+
let outpoints = vec![
212+
OutPoint {
213+
txid: Txid::from_slice(&[8u8; 32]).unwrap(),
214+
vout: 0,
215+
},
216+
OutPoint {
217+
txid: Txid::from_slice(&[5u8; 32]).unwrap(),
218+
vout: 100,
219+
},
220+
];
221+
222+
let result = get_smallest_lexicographic_outpoint(&outpoints);
223+
224+
let mut expected_bytes = [5u8; 36];
225+
expected_bytes[32..36].copy_from_slice(&100u32.to_le_bytes());
226+
assert_eq!(result, expected_bytes);
227+
}
228+
229+
#[test]
230+
fn test_get_smallest_outpoint_txid_endianness_matters() {
231+
// big endian: 0x[00][00][00][01]
232+
// big endian: 0x[a1][b1][c1][d1]
233+
let mut txid_bytes_be = [0u8; 32];
234+
txid_bytes_be[0] = 1;
235+
236+
// little endian: 0x[01][00][00][00]
237+
// little endian: 0x[a2][b2][c2][d2]
238+
let mut txid_bytes_le = [0u8; 32];
239+
txid_bytes_le[31] = 1;
240+
241+
let outpoints = vec![
242+
OutPoint {
243+
txid: Txid::from_slice(&txid_bytes_be).unwrap(),
244+
vout: 1,
245+
},
246+
OutPoint {
247+
txid: Txid::from_slice(&txid_bytes_le).unwrap(),
248+
vout: 1,
249+
},
250+
];
251+
252+
// if Txid is big endian then: [a1] < [a2] => expected_bytes = txid_bytes_be
253+
// if Txid is little endian then: [d2] < [d1] => expected_bytes = txid_bytes_le
254+
let result = get_smallest_lexicographic_outpoint(&outpoints);
255+
256+
let mut expected_bytes = [0u8; 36];
257+
expected_bytes[31] = 1;
258+
expected_bytes[32..36].copy_from_slice(&1u32.to_le_bytes());
259+
260+
assert_eq!(result, expected_bytes);
261+
}
262+
}

0 commit comments

Comments
 (0)