1use acir::BlackBoxFunc;
2
3use k256::{
4 AffinePoint, ProjectivePoint, PublicKey,
5 elliptic_curve::{
6 PrimeField,
7 ops::Reduce,
8 scalar::IsHigh,
9 sec1::{Coordinates, FromSec1Point, Sec1Point, ToSec1Point},
10 },
11};
12use k256::{Scalar, ecdsa::Signature};
13
14use crate::BlackBoxResolutionError;
15
16pub(super) fn verify_signature(
37 hashed_msg: &[u8; 32],
38 public_key_x_bytes: &[u8; 32],
39 public_key_y_bytes: &[u8; 32],
40 signature: &[u8; 64],
41) -> Result<bool, BlackBoxResolutionError> {
42 let Ok(signature) = Signature::try_from(signature.as_slice()) else {
44 log::warn!("Signature provided for ECDSA verification is zero");
46 return Ok(false);
47 };
48
49 let point = Sec1Point::<k256::Secp256k1>::from_affine_coordinates(
50 public_key_x_bytes.into(),
51 public_key_y_bytes.into(),
52 false,
53 );
54
55 let pubkey = PublicKey::from_sec1_point(&point);
56 if pubkey.is_none().into() {
57 log::warn!("Invalid public key provided for ECDSA verification");
59 return Ok(false);
60 }
61 let pubkey = pubkey.unwrap();
62
63 let z = <Scalar as Reduce<k256::U256>>::reduce(&k256::U256::from_be_slice(hashed_msg));
67
68 let r = signature.r();
71 let s = signature.s();
72
73 if s.is_high().into() {
75 log::warn!(
76 "Signature provided for ECDSA verification is not properly normalized (high S value)"
77 );
78 return Ok(false);
79 }
80
81 let s_inv = s.invert().unwrap();
82 let u1 = z * s_inv;
83 let u2 = *r * s_inv;
84
85 #[allow(non_snake_case)]
86 let R: AffinePoint = ((ProjectivePoint::GENERATOR * u1)
87 + (ProjectivePoint::from(*pubkey.as_affine()) * u2))
88 .to_affine();
89
90 match R.to_sec1_point(false).coordinates() {
92 Coordinates::Uncompressed { x, y: _ } => {
93 Ok(Scalar::from_repr(*x).into_option().map_or_else(
97 || {
98 log::warn!(
99 "ECDSA Secp256k1 verification: R.x coordinate exceeds scalar field order - signature is invalid"
100 );
101 false
102 },
103 |scalar| scalar == *r,
104 ))
105 }
106 Coordinates::Identity => Ok(false),
107 _ => Err(BlackBoxResolutionError::Failed(
108 BlackBoxFunc::EcdsaSecp256k1,
109 "Unexpected coordinate encoding".to_string(),
110 )),
111 }
112}
113
114#[cfg(test)]
115mod secp256k1_tests {
116 use super::verify_signature;
117
118 const HASHED_MESSAGE: [u8; 32] = [
120 0x3a, 0x73, 0xf4, 0x12, 0x3a, 0x5c, 0xd2, 0x12, 0x1f, 0x21, 0xcd, 0x7e, 0x8d, 0x35, 0x88,
121 0x35, 0x47, 0x69, 0x49, 0xd0, 0x35, 0xd9, 0xc2, 0xda, 0x68, 0x06, 0xb4, 0x63, 0x3a, 0xc8,
122 0xc1, 0xe2,
123 ];
124 const PUB_KEY_X: [u8; 32] = [
126 0xa0, 0x43, 0x4d, 0x9e, 0x47, 0xf3, 0xc8, 0x62, 0x35, 0x47, 0x7c, 0x7b, 0x1a, 0xe6, 0xae,
127 0x5d, 0x34, 0x42, 0xd4, 0x9b, 0x19, 0x43, 0xc2, 0xb7, 0x52, 0xa6, 0x8e, 0x2a, 0x47, 0xe2,
128 0x47, 0xc7,
129 ];
130 const PUB_KEY_Y: [u8; 32] = [
132 0x89, 0x3a, 0xba, 0x42, 0x54, 0x19, 0xbc, 0x27, 0xa3, 0xb6, 0xc7, 0xe6, 0x93, 0xa2, 0x4c,
133 0x69, 0x6f, 0x79, 0x4c, 0x2e, 0xd8, 0x77, 0xa1, 0x59, 0x3c, 0xbe, 0xe5, 0x3b, 0x03, 0x73,
134 0x68, 0xd7,
135 ];
136 const SIGNATURE: [u8; 64] = [
138 0xe5, 0x08, 0x1c, 0x80, 0xab, 0x42, 0x7d, 0xc3, 0x70, 0x34, 0x6f, 0x4a, 0x0e, 0x31, 0xaa,
139 0x2b, 0xad, 0x8d, 0x97, 0x98, 0xc3, 0x80, 0x61, 0xdb, 0x9a, 0xe5, 0x5a, 0x4e, 0x8d, 0xf4,
140 0x54, 0xfd, 0x28, 0x11, 0x98, 0x94, 0x34, 0x4e, 0x71, 0xb7, 0x87, 0x70, 0xcc, 0x93, 0x1d,
141 0x61, 0xf4, 0x80, 0xec, 0xbb, 0x0b, 0x89, 0xd6, 0xeb, 0x69, 0x69, 0x01, 0x61, 0xe4, 0x9a,
142 0x71, 0x5f, 0xcd, 0x55,
143 ];
144
145 #[test]
146 fn verifies_valid_signature_with_low_s_value() {
147 let valid = verify_signature(&HASHED_MESSAGE, &PUB_KEY_X, &PUB_KEY_Y, &SIGNATURE).unwrap();
148
149 assert!(valid);
150 }
151
152 #[test]
153 fn signature_does_not_verify_on_signature_that_does_not_have_the_full_y_coordinate() {
154 let mut pub_key_y_bytes = [0u8; 32];
155 pub_key_y_bytes[31] = PUB_KEY_Y[31];
156
157 let result =
158 verify_signature(&HASHED_MESSAGE, &PUB_KEY_X, &pub_key_y_bytes, &SIGNATURE).unwrap();
159 assert!(!result);
160 }
161
162 #[test]
163 fn signature_does_not_verify_on_invalid_signature() {
164 let invalid_signature: [u8; 64] = [0x00; 64];
166
167 let result =
168 verify_signature(&HASHED_MESSAGE, &PUB_KEY_X, &PUB_KEY_Y, &invalid_signature).unwrap();
169 assert!(!result);
170 }
171
172 #[test]
173 fn signature_does_not_verify_on_invalid_public_key() {
174 let invalid_pub_key_x: [u8; 32] = [0xff; 32];
175 let invalid_pub_key_y: [u8; 32] = [0xff; 32];
176
177 let result =
178 verify_signature(&HASHED_MESSAGE, &invalid_pub_key_x, &invalid_pub_key_y, &SIGNATURE)
179 .unwrap();
180 assert!(!result);
181 }
182
183 #[test]
184 fn signature_does_not_verify_when_hashed_msg_exceeds_curve_order() {
185 let oversized_hash: [u8; 32] = [0xff; 32];
189
190 let result = verify_signature(&oversized_hash, &PUB_KEY_X, &PUB_KEY_Y, &SIGNATURE).unwrap();
192 assert!(!result);
193 }
194
195 #[test]
196 fn signature_does_not_verify_when_hashed_msg_equals_curve_order() {
197 let curve_order: [u8; 32] = [
199 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
200 0xFF, 0xFE, 0xBA, 0xAE, 0xDC, 0xE6, 0xAF, 0x48, 0xA0, 0x3B, 0xBF, 0xD2, 0x5E, 0x8C,
201 0xD0, 0x36, 0x41, 0x41,
202 ];
203
204 let result = verify_signature(&curve_order, &PUB_KEY_X, &PUB_KEY_Y, &SIGNATURE).unwrap();
205 assert!(!result);
206 }
207}