@@ -65,16 +65,33 @@ pub fn tag_txin(txin: &TxIn, script_pubkey: &ScriptBuf) -> Option<SpInputs> {
6565}
6666
6767pub 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
8097pub 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