@@ -63,34 +63,62 @@ pub fn tag_txin(txin: &TxIn, script_pubkey: &ScriptBuf) -> Option<SpInputs> {
6363 }
6464}
6565
66- pub fn get_smallest_lexicographic_outpoint ( outpoints : & [ OutPoint ] ) -> [ u8 ; 36 ] {
67- // Find the outpoint with the smallest lexicographic order
68- let smallest = outpoints
69- . iter ( )
70- . min_by ( |a, b| {
71- // Compare txids first
72- let a_txid = a. txid . to_raw_hash ( ) ;
73- let b_txid = b. txid . to_raw_hash ( ) ;
74-
75- // If txids are different, compare them
76- match a_txid. as_byte_array ( ) . cmp ( b_txid. as_byte_array ( ) ) {
77- std:: cmp:: Ordering :: Equal => {
78- // If txids are equal, compare vouts directly
79- let a_vout_bytes = a. vout . to_le_bytes ( ) ;
80- let b_vout_bytes = b. vout . to_le_bytes ( ) ;
81- a_vout_bytes. cmp ( & b_vout_bytes)
66+ #[ derive( Debug ) ]
67+ pub enum LexMinError {
68+ NoMinOutpoint ,
69+ }
70+
71+ impl std:: fmt:: Display for LexMinError {
72+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
73+ match self {
74+ Self :: NoMinOutpoint => write ! ( f, "No minimal outpoint, update at least once" ) ,
75+ }
76+ }
77+ }
78+
79+ #[ derive( Default ) ]
80+ pub struct LexMin < ' a > {
81+ current_min : Option < & ' a OutPoint > ,
82+ }
83+
84+ impl < ' a > LexMin < ' a > {
85+ pub fn update ( & mut self , outpoint : & ' a OutPoint ) -> & ' a OutPoint {
86+ if let Some ( min) = self . current_min {
87+ let new_min = std:: cmp:: min_by ( outpoint, min, |a, b| {
88+ // Compare txids first
89+ let a_txid = a. txid . to_raw_hash ( ) ;
90+ let b_txid = b. txid . to_raw_hash ( ) ;
91+
92+ // If txids are different, compare them
93+ match a_txid. as_byte_array ( ) . cmp ( b_txid. as_byte_array ( ) ) {
94+ std:: cmp:: Ordering :: Equal => {
95+ // If txids are equal, compare vouts directly
96+ let a_vout_bytes = a. vout . to_le_bytes ( ) ;
97+ let b_vout_bytes = b. vout . to_le_bytes ( ) ;
98+ a_vout_bytes. cmp ( & b_vout_bytes)
99+ }
100+ other => other,
82101 }
83- other => other,
84- }
85- } )
86- . expect ( "cannot create silent payment script pubkey without outpoints" ) ;
102+ } ) ;
103+ self . current_min = Some ( new_min) ;
104+ new_min
105+ } else {
106+ self . current_min = Some ( outpoint) ;
107+ outpoint
108+ }
109+ }
87110
88- // Only allocate the result array once we have the smallest outpoint
89- let mut result = [ 0u8 ; 36 ] ;
90- result[ ..32 ] . copy_from_slice ( smallest. txid . to_raw_hash ( ) . as_byte_array ( ) ) ;
91- result[ 32 ..36 ] . copy_from_slice ( & smallest. vout . to_le_bytes ( ) ) ;
111+ pub fn bytes ( & self ) -> Result < [ u8 ; 36 ] , LexMinError > {
112+ if let Some ( min) = self . current_min {
113+ let mut result = [ 0u8 ; 36 ] ;
114+ result[ ..32 ] . copy_from_slice ( min. txid . to_raw_hash ( ) . as_byte_array ( ) ) ;
115+ result[ 32 ..36 ] . copy_from_slice ( & min. vout . to_le_bytes ( ) ) ;
92116
93- result
117+ Ok ( result)
118+ } else {
119+ Err ( LexMinError :: NoMinOutpoint )
120+ }
121+ }
94122}
95123
96124// Do not report coverage for this function as it is a wrapper around external lib function
@@ -110,9 +138,7 @@ pub fn compute_shared_secret(sk: &SecretKey, pk: &PublicKey) -> PublicKey {
110138#[ cfg( test) ]
111139#[ cfg_attr( coverage_nightly, coverage( off) ) ]
112140mod tests {
113- use super :: {
114- get_smallest_lexicographic_outpoint, tag_txin, Hash , ScriptBuf , SpInputs , TxIn , NUMS_H ,
115- } ;
141+ use super :: { tag_txin, Hash , LexMin , ScriptBuf , SpInputs , TxIn , NUMS_H } ;
116142 use bitcoin:: {
117143 hex:: test_hex_unwrap as hex, taproot:: TAPROOT_ANNEX_PREFIX , OutPoint , Sequence , Txid ,
118144 Witness ,
@@ -344,8 +370,9 @@ mod tests {
344370 }
345371
346372 #[ test]
347- fn test_get_smallest_outpoint_different_txids_and_vouts ( ) {
348- let outpoints = vec ! [
373+ fn test_lex_min_different_txids_and_vouts ( ) {
374+ let mut lex_min = LexMin :: default ( ) ;
375+ let outpoints = [
349376 OutPoint {
350377 txid : Txid :: from_slice ( & [ 3u8 ; 32 ] ) . unwrap ( ) ,
351378 vout : 2 ,
@@ -360,7 +387,11 @@ mod tests {
360387 } ,
361388 ] ;
362389
363- let result = get_smallest_lexicographic_outpoint ( & outpoints) ;
390+ for outpoint in outpoints. iter ( ) {
391+ lex_min. update ( outpoint) ;
392+ }
393+
394+ let result = lex_min. bytes ( ) . expect ( "should succeed" ) ;
364395
365396 let mut expected_bytes = [ 2u8 ; 36 ] ;
366397 expected_bytes[ 32 ..36 ] . copy_from_slice ( & 1u32 . to_le_bytes ( ) ) ;
@@ -369,32 +400,37 @@ mod tests {
369400 }
370401
371402 #[ test]
372- #[ should_panic( expected = "cannot create silent payment script pubkey without outpoints" ) ]
373- fn test_get_smallest_outpoint_empty ( ) {
374- let outpoints: Vec < OutPoint > = vec ! [ ] ;
375- get_smallest_lexicographic_outpoint ( & outpoints) ;
403+ fn test_lex_min_no_update ( ) {
404+ let e = LexMin :: default ( ) . bytes ( ) . expect_err ( "should fail" ) ;
405+ assert_eq ! ( "No minimal outpoint, update at least once" , e. to_string( ) ) ;
376406 }
377407
378408 // Additional test: same txid, different vouts
379409 #[ test]
380- fn test_get_smallest_outpoint_identical_txid_different_vouts ( ) {
410+ fn test_lex_min_identical_txid_different_vouts ( ) {
411+ let mut lex_min = LexMin :: default ( ) ;
381412 let txid = Txid :: from_slice ( & [ 0u8 ; 32 ] ) . unwrap ( ) ;
382- let outpoints = vec ! [
413+ let outpoints = [
383414 OutPoint { txid, vout : 10 } ,
384415 OutPoint { txid, vout : 2 } ,
385416 OutPoint { txid, vout : 5 } ,
386417 ] ;
387418
388- let result = get_smallest_lexicographic_outpoint ( & outpoints) ;
419+ for outpoint in outpoints. iter ( ) {
420+ lex_min. update ( outpoint) ;
421+ }
422+
423+ let result = lex_min. bytes ( ) . expect ( "should succeed" ) ;
389424
390425 let mut expected_bytes = [ 0u8 ; 36 ] ;
391426 expected_bytes[ 32 ..36 ] . copy_from_slice ( & 2u32 . to_le_bytes ( ) ) ;
392427 assert_eq ! ( result, expected_bytes) ;
393428 }
394429
395430 #[ test]
396- fn test_get_smallest_outpoint_same_vout_different_txid ( ) {
397- let outpoints = vec ! [
431+ fn test_lex_min_same_vout_different_txid ( ) {
432+ let mut lex_min = LexMin :: default ( ) ;
433+ let outpoints = [
398434 OutPoint {
399435 txid : Txid :: from_slice ( & [ 2u8 ; 32 ] ) . unwrap ( ) ,
400436 vout : 7 ,
@@ -409,16 +445,21 @@ mod tests {
409445 } ,
410446 ] ;
411447
412- let result = get_smallest_lexicographic_outpoint ( & outpoints) ;
448+ for outpoint in outpoints. iter ( ) {
449+ lex_min. update ( outpoint) ;
450+ }
451+
452+ let result = lex_min. bytes ( ) . expect ( "should succeed" ) ;
413453
414454 let mut expected_bytes = [ 1u8 ; 36 ] ;
415455 expected_bytes[ 32 ..36 ] . copy_from_slice ( & 7u32 . to_le_bytes ( ) ) ;
416456 assert_eq ! ( result, expected_bytes) ;
417457 }
418458
419459 #[ test]
420- fn test_get_smallest_outpoint_edge_case_max_vout ( ) {
421- let outpoints = vec ! [
460+ fn test_lex_min_edge_case_max_vout ( ) {
461+ let mut lex_min = LexMin :: default ( ) ;
462+ let outpoints = [
422463 OutPoint {
423464 txid : Txid :: from_slice ( & [ 1u8 ; 32 ] ) . unwrap ( ) ,
424465 vout : u32:: MAX ,
@@ -429,7 +470,11 @@ mod tests {
429470 } ,
430471 ] ;
431472
432- let result = get_smallest_lexicographic_outpoint ( & outpoints) ;
473+ for outpoint in outpoints. iter ( ) {
474+ lex_min. update ( outpoint) ;
475+ }
476+
477+ let result = lex_min. bytes ( ) . expect ( "should succeed" ) ;
433478
434479 let mut expected_bytes = [ 1u8 ; 36 ] ;
435480 expected_bytes[ ..32 ] . copy_from_slice ( & [ 1u8 ; 32 ] ) ;
@@ -438,8 +483,9 @@ mod tests {
438483 }
439484
440485 #[ test]
441- fn test_get_smallest_outpoint_txid_takes_precedence ( ) {
442- let outpoints = vec ! [
486+ fn test_lex_min_txid_takes_precedence ( ) {
487+ let mut lex_min = LexMin :: default ( ) ;
488+ let outpoints = [
443489 OutPoint {
444490 txid : Txid :: from_slice ( & [ 8u8 ; 32 ] ) . unwrap ( ) ,
445491 vout : 0 ,
@@ -450,7 +496,11 @@ mod tests {
450496 } ,
451497 ] ;
452498
453- let result = get_smallest_lexicographic_outpoint ( & outpoints) ;
499+ for outpoint in outpoints. iter ( ) {
500+ lex_min. update ( outpoint) ;
501+ }
502+
503+ let result = lex_min. bytes ( ) . expect ( "should succeed" ) ;
454504
455505 let mut expected_bytes = [ 5u8 ; 36 ] ;
456506 expected_bytes[ 32 ..36 ] . copy_from_slice ( & 100u32 . to_le_bytes ( ) ) ;
@@ -459,6 +509,7 @@ mod tests {
459509
460510 #[ test]
461511 fn test_get_smallest_outpoint_txid_endianness_matters ( ) {
512+ let mut lex_min = LexMin :: default ( ) ;
462513 // big endian: 0x[00][00][00][01]
463514 // big endian: 0x[a1][b1][c1][d1]
464515 let mut txid_bytes_be = [ 0u8 ; 32 ] ;
@@ -469,7 +520,7 @@ mod tests {
469520 let mut txid_bytes_le = [ 0u8 ; 32 ] ;
470521 txid_bytes_le[ 31 ] = 1 ;
471522
472- let outpoints = vec ! [
523+ let outpoints = [
473524 OutPoint {
474525 txid : Txid :: from_slice ( & txid_bytes_be) . unwrap ( ) ,
475526 vout : 1 ,
@@ -480,9 +531,13 @@ mod tests {
480531 } ,
481532 ] ;
482533
534+ for outpoint in outpoints. iter ( ) {
535+ lex_min. update ( outpoint) ;
536+ }
537+
483538 // if Txid is big endian then: [a1] < [a2] => expected_bytes = txid_bytes_be
484539 // if Txid is little endian then: [d2] < [d1] => expected_bytes = txid_bytes_le
485- let result = get_smallest_lexicographic_outpoint ( & outpoints ) ;
540+ let result = lex_min . bytes ( ) . expect ( "should succeed" ) ;
486541
487542 let mut expected_bytes = [ 0u8 ; 36 ] ;
488543 expected_bytes[ 31 ] = 1 ;
0 commit comments