1use core::fmt;
2
3use eth_valkyoth_hash::Keccak256;
4use eth_valkyoth_primitives::Address;
5use eth_valkyoth_protocol::SetCodeAuthorization;
6
7#[cfg(feature = "secp256k1-k256")]
8use crate::K256Secp256k1Backend;
9use crate::{
10 EthereumSignature, RecoverableSecp256k1, SetCodeAuthorizationSigningHash,
11 TransactionSigningHashError, recover_sender_from_digest_with_backend,
12 set_code_authorization_signing_hash,
13};
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct ValidatedSetCodeAuthorization {
18 authority: Address,
19 signing_hash: SetCodeAuthorizationSigningHash,
20}
21
22impl ValidatedSetCodeAuthorization {
23 #[must_use]
25 pub(crate) const fn new(
26 authority: Address,
27 signing_hash: SetCodeAuthorizationSigningHash,
28 ) -> Self {
29 Self {
30 authority,
31 signing_hash,
32 }
33 }
34
35 #[must_use]
37 pub const fn authority(self) -> Address {
38 self.authority
39 }
40
41 #[must_use]
43 pub const fn signing_hash(self) -> SetCodeAuthorizationSigningHash {
44 self.signing_hash
45 }
46}
47
48#[non_exhaustive]
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub enum SetCodeAuthorizationValidationError {
52 SigningHash(TransactionSigningHashError),
54 InvalidSignature,
56 WrongAuthority,
58}
59
60impl SetCodeAuthorizationValidationError {
61 #[must_use]
63 pub const fn code(self) -> &'static str {
64 match self {
65 Self::SigningHash(error) => error.code(),
66 Self::InvalidSignature => "ETH_SET_CODE_AUTHORIZATION_SIGNATURE_INVALID",
67 Self::WrongAuthority => "ETH_SET_CODE_AUTHORIZATION_WRONG_AUTHORITY",
68 }
69 }
70
71 #[must_use]
73 pub const fn message(self) -> &'static str {
74 match self {
75 Self::SigningHash(_) => "set-code authorization signing hash construction failed",
76 Self::InvalidSignature => "set-code authorization signature is not accepted",
77 Self::WrongAuthority => {
78 "set-code authorization signature recovered a different authority"
79 }
80 }
81 }
82
83 #[must_use]
85 pub const fn category(self) -> SetCodeAuthorizationValidationErrorCategory {
86 match self {
87 Self::SigningHash(_) => SetCodeAuthorizationValidationErrorCategory::SigningHash,
88 Self::InvalidSignature | Self::WrongAuthority => {
89 SetCodeAuthorizationValidationErrorCategory::Signature
90 }
91 }
92 }
93}
94
95impl fmt::Display for SetCodeAuthorizationValidationError {
96 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter.write_str(self.message())
98 }
99}
100
101#[cfg(feature = "std")]
102impl std::error::Error for SetCodeAuthorizationValidationError {}
103
104#[non_exhaustive]
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107pub enum SetCodeAuthorizationValidationErrorCategory {
108 SigningHash,
110 Signature,
112}
113
114#[cfg(feature = "secp256k1-k256")]
121pub fn validate_set_code_authorization_signature<H1, H2>(
122 authorization: SetCodeAuthorization,
123 expected_authority: Option<Address>,
124 scratch: &mut [u8],
125 signing_hasher: H1,
126 address_hasher: H2,
127) -> Result<ValidatedSetCodeAuthorization, SetCodeAuthorizationValidationError>
128where
129 H1: Keccak256,
130 H2: Keccak256,
131{
132 validate_set_code_authorization_signature_with_backend(
133 authorization,
134 expected_authority,
135 scratch,
136 signing_hasher,
137 K256Secp256k1Backend,
138 address_hasher,
139 )
140}
141
142pub fn validate_set_code_authorization_signature_with_backend<B, H1, H2>(
144 authorization: SetCodeAuthorization,
145 expected_authority: Option<Address>,
146 scratch: &mut [u8],
147 signing_hasher: H1,
148 secp256k1_backend: B,
149 address_hasher: H2,
150) -> Result<ValidatedSetCodeAuthorization, SetCodeAuthorizationValidationError>
151where
152 B: RecoverableSecp256k1,
153 H1: Keccak256,
154 H2: Keccak256,
155{
156 let signing_hash = set_code_authorization_signing_hash(authorization, scratch, signing_hasher)
157 .map_err(SetCodeAuthorizationValidationError::SigningHash)?;
158 let signature =
159 EthereumSignature::from_parts(authorization.r, authorization.s, authorization.y_parity);
160 let authority = recover_sender_from_digest_with_backend(
161 signing_hash.to_b256(),
162 signature,
163 secp256k1_backend,
164 address_hasher,
165 )
166 .map_err(|_| SetCodeAuthorizationValidationError::InvalidSignature)?;
167 if let Some(expected) = expected_authority
168 && authority != expected
169 {
170 return Err(SetCodeAuthorizationValidationError::WrongAuthority);
171 }
172 Ok(ValidatedSetCodeAuthorization::new(authority, signing_hash))
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::test_crypto::{RealKeccak, TestSecp256k1Backend};
179 use eth_valkyoth_primitives::Nonce;
180 use eth_valkyoth_protocol::{
181 SET_CODE_AUTHORIZATION_MAGIC, SetCodeAuthorizationChainId, SignatureYParity,
182 encode_set_code_authorization_signing_preimage,
183 };
184 use k256::ecdsa::SigningKey;
185
186 fn signing_key() -> Result<SigningKey, SetCodeAuthorizationValidationError> {
187 SigningKey::from_bytes((&fixture_bytes(b"set-code authorization test key")).into())
188 .map_err(|_| SetCodeAuthorizationValidationError::InvalidSignature)
189 }
190
191 fn unsigned_authorization() -> SetCodeAuthorization {
192 let mut chain_id = [0_u8; 32];
193 if let Some(last) = chain_id.last_mut() {
194 *last = u8::try_from("chain-one".len()).unwrap_or_default();
195 }
196 SetCodeAuthorization {
197 chain_id: SetCodeAuthorizationChainId::from_be_bytes(chain_id),
198 address: Address::from_bytes(address_bytes(b"delegate")),
199 nonce: Nonce::new(u64::try_from("nonce".len()).unwrap_or_default()),
200 y_parity: SignatureYParity::Even,
201 r: [0_u8; 32],
202 s: [0_u8; 32],
203 }
204 }
205
206 fn signed_authorization() -> Result<SetCodeAuthorization, SetCodeAuthorizationValidationError> {
207 let authorization = unsigned_authorization();
208 let mut scratch = [0_u8; 128];
209 let signing_hash =
210 set_code_authorization_signing_hash(authorization, &mut scratch, RealKeccak::default())
211 .map_err(SetCodeAuthorizationValidationError::SigningHash)?;
212 let key = signing_key()?;
213 let (signature, recovery_id) = key
214 .sign_prehash_recoverable(&<[u8; 32]>::from(signing_hash.to_b256()))
215 .map_err(|_| SetCodeAuthorizationValidationError::InvalidSignature)?;
216 let bytes = signature.to_bytes();
217 let r = bytes
218 .get(..32)
219 .and_then(|value| <[u8; 32]>::try_from(value).ok())
220 .ok_or(SetCodeAuthorizationValidationError::InvalidSignature)?;
221 let s = bytes
222 .get(32..)
223 .and_then(|value| <[u8; 32]>::try_from(value).ok())
224 .ok_or(SetCodeAuthorizationValidationError::InvalidSignature)?;
225 let y_parity = SignatureYParity::try_new(u64::from(recovery_id.to_byte()))
226 .map_err(|_| SetCodeAuthorizationValidationError::InvalidSignature)?;
227 Ok(SetCodeAuthorization {
228 y_parity,
229 r,
230 s,
231 ..authorization
232 })
233 }
234
235 fn expected_authority() -> Result<Address, SetCodeAuthorizationValidationError> {
236 let key = signing_key()?;
237 let encoded = key.verifying_key().to_encoded_point(false);
238 let public_key = encoded
239 .as_bytes()
240 .get(1..)
241 .ok_or(SetCodeAuthorizationValidationError::InvalidSignature)?;
242 let digest = eth_valkyoth_hash::hash_one(RealKeccak::default(), public_key);
243 let bytes = <[u8; 32]>::from(digest);
244 let address = bytes
245 .get(12..)
246 .and_then(|value| <[u8; 20]>::try_from(value).ok())
247 .ok_or(SetCodeAuthorizationValidationError::InvalidSignature)?;
248 Ok(Address::from_bytes(address))
249 }
250
251 fn fixture_bytes(label: &[u8]) -> [u8; 32] {
252 let mut bytes = [0_u8; 32];
253 fill_fixture_bytes(&mut bytes, label);
254 bytes
255 }
256
257 fn address_bytes(label: &[u8]) -> [u8; 20] {
258 let mut bytes = [0_u8; 20];
259 fill_fixture_bytes(&mut bytes, label);
260 bytes
261 }
262
263 fn fill_fixture_bytes(bytes: &mut [u8], label: &[u8]) {
264 if label.is_empty() {
265 return;
266 }
267 for (index, byte) in bytes.iter_mut().enumerate() {
268 let Some(label_index) = index.checked_rem(label.len()) else {
269 return;
270 };
271 let label_byte = label.get(label_index).copied().unwrap_or_default();
272 *byte = label_byte.wrapping_add(u8::try_from(index).unwrap_or_default());
273 }
274 }
275
276 #[test]
277 fn validates_authorization_signature_in_authorization_domain() {
278 let authorization = signed_authorization();
279 let expected = expected_authority();
280 assert!(authorization.is_ok());
281 assert!(expected.is_ok());
282
283 if let (Ok(authorization), Ok(expected)) = (authorization, expected) {
284 let mut scratch = [0_u8; 128];
285 let result = validate_set_code_authorization_signature_with_backend(
286 authorization,
287 Some(expected),
288 &mut scratch,
289 RealKeccak::default(),
290 TestSecp256k1Backend,
291 RealKeccak::default(),
292 );
293 assert_eq!(
294 result.map(ValidatedSetCodeAuthorization::authority),
295 Ok(expected)
296 );
297 }
298 }
299
300 #[test]
301 fn authorization_preimage_uses_magic_domain() {
302 let authorization = unsigned_authorization();
303 let mut output = [0_u8; 128];
304 let written = encode_set_code_authorization_signing_preimage(authorization, &mut output);
305 assert!(written.is_ok());
306 if let Ok(written) = written {
307 assert_eq!(output.first(), Some(&SET_CODE_AUTHORIZATION_MAGIC));
308 assert_ne!(output.get(1..written), Some(&[][..]));
309 }
310 }
311
312 #[test]
313 fn rejects_wrong_authority_and_short_scratch() {
314 let authorization = signed_authorization();
315 assert!(authorization.is_ok());
316 if let Ok(authorization) = authorization {
317 let mut scratch = [0_u8; 128];
318 assert_eq!(
319 validate_set_code_authorization_signature_with_backend(
320 authorization,
321 Some(Address::from_bytes(address_bytes(b"wrong"))),
322 &mut scratch,
323 RealKeccak::default(),
324 TestSecp256k1Backend,
325 RealKeccak::default(),
326 ),
327 Err(SetCodeAuthorizationValidationError::WrongAuthority)
328 );
329
330 let mut short = [0_u8; 8];
331 assert!(matches!(
332 validate_set_code_authorization_signature_with_backend(
333 authorization,
334 None,
335 &mut short,
336 RealKeccak::default(),
337 TestSecp256k1Backend,
338 RealKeccak::default(),
339 ),
340 Err(SetCodeAuthorizationValidationError::SigningHash(_))
341 ));
342 }
343 }
344}