Skip to content

Commit 9de6acc

Browse files
committed
fix: allow secp256k1-recover? to accept high-S signatures
1 parent f8d02cd commit 9de6acc

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

clarity/src/vm/tests/crypto.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,29 @@ fn zeroed_buff_literal(len: usize) -> String {
4949
buff_literal(&vec![0u8; len])
5050
}
5151

52+
#[test]
53+
fn test_secp256k1_recover_accepts_high_s_signature() {
54+
let message = "0x7147f89f7ba4980c8628b52c2f0351f018ed31ba593e5ed676ad428c67c23ffb";
55+
let signature = "0xe120eaed297a125259ee235a702c3f8dc18f8e65cdb28625061dd9e80197b0e6d29c9b9a200ecffee51033a93c896e9e00907789888eef42f3ede3a81dd7730201";
56+
let expected_pubkey = "0x034170a2083dccbc2be253885a8d0e9f7ce859eb370d0c5cae3b6994af4cb9d666";
57+
let fallback = zeroed_buff_literal(33);
58+
let program = format!(
59+
"(is-eq (unwrap! (secp256k1-recover? {message} {signature}) {fallback}) {expected_pubkey})"
60+
);
61+
62+
assert_eq!(
63+
Value::Bool(true),
64+
execute_with_parameters(
65+
program.as_str(),
66+
ClarityVersion::Clarity1,
67+
StacksEpochId::Epoch20,
68+
false
69+
)
70+
.expect("execution should succeed")
71+
.expect("should return a value")
72+
);
73+
}
74+
5275
#[test]
5376
fn test_secp256r1_verify_valid_signature_returns_true() {
5477
let (message, signature, pubkey) = secp256r1_vectors();

stacks-common/src/util/secp256k1.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,14 +475,25 @@ pub fn secp256k1_recover(
475475
return Err(Secp256k1Error::InvalidSignature);
476476
}
477477

478-
let recovery_id = K256RecoveryId::from_byte(serialized_signature_arr[64])
478+
let mut recovery_id = K256RecoveryId::from_byte(serialized_signature_arr[64])
479479
.ok_or(Secp256k1Error::InvalidRecoveryId)?;
480480

481481
let signature = K256Signature::from_slice(&serialized_signature_arr[..64])
482482
.map_err(|_| Secp256k1Error::InvalidSignature)?;
483483

484+
let normalized_signature = signature.normalize_s();
485+
486+
let signature_ref: &K256Signature = if let Some(normalized) = normalized_signature.as_ref() {
487+
let flipped_recovery_id =
488+
K256RecoveryId::new(!recovery_id.is_y_odd(), recovery_id.is_x_reduced());
489+
recovery_id = flipped_recovery_id;
490+
normalized
491+
} else {
492+
&signature
493+
};
494+
484495
let recovered_pub =
485-
K256VerifyingKey::recover_from_prehash(message_arr, &signature, recovery_id)
496+
K256VerifyingKey::recover_from_prehash(message_arr, signature_ref, recovery_id)
486497
.map_err(|_| Secp256k1Error::RecoveryFailed)?;
487498

488499
let public_key = K256PublicKey::from(&recovered_pub);

0 commit comments

Comments
 (0)