@@ -250,12 +250,22 @@ impl Secp256k1PublicKey {
250250 . to_secp256k1_recoverable ( )
251251 . ok_or ( "Invalid signature: failed to decode recoverable signature" ) ?;
252252
253- let recovered_key = K256VerifyingKey :: recover_from_prehash (
254- msg,
255- & recoverable_sig. signature ,
256- recoverable_sig. recovery_id ,
257- )
258- . map_err ( |_| "Invalid signature: failed to recover public key" ) ?;
253+ let RecoverableSignature {
254+ signature,
255+ recovery_id,
256+ } = recoverable_sig;
257+
258+ let normalized_signature = signature. normalize_s ( ) ;
259+ let ( signature_ref, recovery_id) = if let Some ( normalized) = normalized_signature. as_ref ( ) {
260+ let flipped_recovery =
261+ K256RecoveryId :: new ( !recovery_id. is_y_odd ( ) , recovery_id. is_x_reduced ( ) ) ;
262+ ( normalized, flipped_recovery)
263+ } else {
264+ ( & signature, recovery_id)
265+ } ;
266+
267+ let recovered_key = K256VerifyingKey :: recover_from_prehash ( msg, signature_ref, recovery_id)
268+ . map_err ( |_| "Invalid signature: failed to recover public key" ) ?;
259269
260270 Ok ( Secp256k1PublicKey {
261271 key : recovered_key,
@@ -536,7 +546,12 @@ pub fn secp256k1_verify(
536546
537547#[ cfg( test) ]
538548mod tests {
549+ use proptest:: prelude:: * ;
539550 use rand:: RngCore ;
551+ use stacks_common_v3_1_00_13:: types:: {
552+ PrivateKey as LegacyPrivateKey , PublicKey as LegacyPublicKey ,
553+ } ;
554+ use stacks_common_v3_1_00_13:: util:: secp256k1 as legacy;
540555
541556 use super :: * ;
542557 use crate :: util:: get_epoch_time_ms;
@@ -1023,4 +1038,137 @@ mod tests {
10231038 panic ! ( "High-s signature should normalize to low-s" ) ;
10241039 }
10251040 }
1041+
1042+ fn is_high_s_signature ( sig_bytes : & [ u8 ] ) -> bool {
1043+ assert_eq ! ( sig_bytes. len( ) , 64 ) ;
1044+ let signature =
1045+ K256Signature :: from_slice ( sig_bytes) . expect ( "signature bytes should form valid r,s" ) ;
1046+ signature. normalize_s ( ) . is_some ( )
1047+ }
1048+
1049+ fn to_high_s_rsv ( signature_rsv : & [ u8 ] ) -> Vec < u8 > {
1050+ assert_eq ! ( signature_rsv. len( ) , 65 ) ;
1051+ if is_high_s_signature ( & signature_rsv[ ..64 ] ) {
1052+ return signature_rsv. to_vec ( ) ;
1053+ }
1054+
1055+ let low_sig =
1056+ K256Signature :: from_slice ( & signature_rsv[ ..64 ] ) . expect ( "valid signature bytes" ) ;
1057+ let ( r, s) = ( low_sig. r ( ) , low_sig. s ( ) ) ;
1058+ let high_sig =
1059+ K256Signature :: from_scalars ( * r, -* s) . expect ( "valid signature when flipping s" ) ;
1060+ let mut signature = signature_rsv. to_vec ( ) ;
1061+ let high_bytes = high_sig. to_bytes ( ) ;
1062+ signature[ ..64 ] . copy_from_slice ( high_bytes. as_slice ( ) ) ;
1063+ signature[ 64 ] ^= 0x01 ;
1064+
1065+ assert ! ( is_high_s_signature( & signature[ ..64 ] ) ) ;
1066+ signature
1067+ }
1068+
1069+ proptest ! {
1070+ #[ test]
1071+ fn prop_from_seed_matches_legacy( seed in proptest:: collection:: vec( any:: <u8 >( ) , 1 ..128 ) ) {
1072+ let new_sk = Secp256k1PrivateKey :: from_seed( & seed) ;
1073+ let legacy_sk = legacy:: Secp256k1PrivateKey :: from_seed( & seed) ;
1074+
1075+ prop_assert_eq!( new_sk. to_hex( ) , legacy_sk. to_hex( ) ) ;
1076+
1077+ let new_pk = Secp256k1PublicKey :: from_private( & new_sk) ;
1078+ let legacy_pk = legacy:: Secp256k1PublicKey :: from_private( & legacy_sk) ;
1079+
1080+ prop_assert_eq!( new_pk. to_hex( ) , legacy_pk. to_hex( ) ) ;
1081+ }
1082+
1083+ #[ test]
1084+ fn prop_low_s_paths_match_legacy(
1085+ seed in proptest:: collection:: vec( any:: <u8 >( ) , 1 ..128 ) ,
1086+ msg in any:: <[ u8 ; 32 ] >( )
1087+ ) {
1088+ let new_sk = Secp256k1PrivateKey :: from_seed( & seed) ;
1089+ let legacy_sk = legacy:: Secp256k1PrivateKey :: from_seed( & seed) ;
1090+
1091+ let new_pk = Secp256k1PublicKey :: from_private( & new_sk) ;
1092+ let legacy_pk = legacy:: Secp256k1PublicKey :: from_private( & legacy_sk) ;
1093+
1094+ let sig_new = new_sk. sign( & msg) . expect( "new signature" ) ;
1095+ let sig_legacy = legacy_sk. sign( & msg) . expect( "legacy signature" ) ;
1096+
1097+ prop_assert_eq!( sig_new. to_rsv( ) , sig_legacy. to_rsv( ) ) ;
1098+
1099+ prop_assert!( new_pk. verify( & msg, & sig_new) . expect( "new verify low-S" ) ) ;
1100+ prop_assert!( legacy_pk. verify( & msg, & sig_legacy) . expect( "legacy verify low-S" ) ) ;
1101+
1102+ let recovered_new = Secp256k1PublicKey :: recover_to_pubkey( & msg, & sig_new)
1103+ . expect( "new recover" ) ;
1104+ let recovered_legacy = legacy:: Secp256k1PublicKey :: recover_to_pubkey( & msg, & sig_legacy)
1105+ . expect( "legacy recover" ) ;
1106+ prop_assert_eq!( recovered_new. to_hex( ) , recovered_legacy. to_hex( ) ) ;
1107+
1108+ let sig_rsv = sig_new. to_rsv( ) ;
1109+ let pk_bytes_new = new_pk. to_bytes_compressed( ) ;
1110+ let pk_bytes_legacy = legacy_pk. to_bytes( ) ;
1111+
1112+ let recover_new = secp256k1_recover( & msg, & sig_rsv) ;
1113+ let recover_legacy = legacy:: secp256k1_recover( & msg, & sig_rsv) ;
1114+ prop_assert!( recover_new. is_ok( ) ) ;
1115+ prop_assert!( recover_legacy. is_ok( ) ) ;
1116+ prop_assert_eq!( recover_new. unwrap( ) , recover_legacy. unwrap( ) ) ;
1117+
1118+ let verify_new = secp256k1_verify( & msg, & sig_rsv, & pk_bytes_new) ;
1119+ let verify_legacy = legacy:: secp256k1_verify( & msg, & sig_rsv, & pk_bytes_legacy) ;
1120+ prop_assert!( verify_new. is_ok( ) ) ;
1121+ prop_assert!( verify_legacy. is_ok( ) ) ;
1122+ }
1123+
1124+ #[ test]
1125+ fn prop_high_s_paths_match_legacy(
1126+ seed in proptest:: collection:: vec( any:: <u8 >( ) , 1 ..128 ) ,
1127+ msg in any:: <[ u8 ; 32 ] >( )
1128+ ) {
1129+ let new_sk = Secp256k1PrivateKey :: from_seed( & seed) ;
1130+ let legacy_sk = legacy:: Secp256k1PrivateKey :: from_seed( & seed) ;
1131+
1132+ let new_pk = Secp256k1PublicKey :: from_private( & new_sk) ;
1133+ let legacy_pk = legacy:: Secp256k1PublicKey :: from_private( & legacy_sk) ;
1134+
1135+ let sig_new = new_sk. sign( & msg) . expect( "new signature" ) ;
1136+
1137+ let high_s_rsv = to_high_s_rsv( & sig_new. to_rsv( ) ) ;
1138+ prop_assert!( is_high_s_signature( & high_s_rsv[ ..64 ] ) ) ;
1139+
1140+ let mut high_s_vrs = high_s_rsv. clone( ) ;
1141+ high_s_vrs. rotate_right( 1 ) ;
1142+
1143+ let high_s_new = MessageSignature :: from_raw( & high_s_vrs) ;
1144+ let high_s_legacy = legacy:: MessageSignature :: from_raw( & high_s_vrs) ;
1145+
1146+ let verify_new = new_pk. verify( & msg, & high_s_new) ;
1147+ let verify_legacy = legacy_pk. verify( & msg, & high_s_legacy) ;
1148+ prop_assert_eq!(
1149+ verify_new. map_err( |_| ( ) ) ,
1150+ verify_legacy. map_err( |_| ( ) )
1151+ ) ;
1152+ if let Ok ( valid) = verify_new {
1153+ prop_assert!( !valid) ;
1154+ }
1155+
1156+ let recovered_new = Secp256k1PublicKey :: recover_to_pubkey( & msg, & high_s_new)
1157+ . map( |pk| pk. to_hex( ) )
1158+ . map_err( |_| ( ) ) ;
1159+ let recovered_legacy =
1160+ legacy:: Secp256k1PublicKey :: recover_to_pubkey( & msg, & high_s_legacy)
1161+ . map( |pk| pk. to_hex( ) )
1162+ . map_err( |_| ( ) ) ;
1163+ prop_assert_eq!( recovered_new, recovered_legacy) ;
1164+
1165+ let recover_new = secp256k1_recover( & msg, & high_s_rsv) . map_err( |_| ( ) ) ;
1166+ let recover_legacy = legacy:: secp256k1_recover( & msg, & high_s_rsv) . map_err( |_| ( ) ) ;
1167+ prop_assert_eq!( recover_new, recover_legacy) ;
1168+
1169+ let verify_new = secp256k1_verify( & msg, & high_s_rsv, & new_pk. to_bytes_compressed( ) ) ;
1170+ let verify_legacy = legacy:: secp256k1_verify( & msg, & high_s_rsv, & legacy_pk. to_bytes( ) ) ;
1171+ prop_assert_eq!( verify_new. is_ok( ) , verify_legacy. is_ok( ) ) ;
1172+ }
1173+ }
10261174}
0 commit comments