mod.rs
1 // Copyright (c) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the alphavm library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 #[cfg(test)] 17 pub mod tests; 18 19 mod serialize; 20 21 use super::*; 22 use alphavm_utilities::bytes_from_bits_le; 23 24 use k256::{ 25 Secp256k1, 26 ecdsa::{ 27 RecoveryId as ECDSARecoveryId, 28 Signature, 29 SigningKey, 30 VerifyingKey, 31 signature::hazmat::{PrehashSigner, PrehashVerifier}, 32 }, 33 elliptic_curve::{Curve, generic_array::typenum::Unsigned}, 34 }; 35 36 /// The recovery ID for an ECDSA/Secp256k1 signature. 37 #[derive(Clone, PartialEq, Eq)] 38 pub struct RecoveryID { 39 /// The recovery ID. 40 pub recovery_id: ECDSARecoveryId, 41 /// The Ethereum chain ID (if applicable). 42 /// None = non-Ethereum canonical 43 /// Some(0) = ETH legacy (v = 27 or 28) 44 /// Some(id>=1) = EIP-155(id). 45 pub chain_id: Option<u64>, 46 } 47 48 impl RecoveryID { 49 /// The offset to add to the recovery ID for EIP-155 signatures (>= 35). 50 const ETH_EIP155_OFFSET: u8 = 35; 51 /// The offset to add to the recovery ID for legacy Ethereum signatures (27 or 28). 52 const ETH_LEGACY_OFFSET: u8 = 27; 53 54 /// For your *byte* encoding (not raw Ethereum tx `v`): 55 /// - Standard (None): write 0..=3 56 /// - ETH legacy (Some(0)): write 27/28 57 /// - EIP-155 (Some(id>=1)): still just write y (0/1); full `v` is derived via `to_eth_v`. 58 #[inline] 59 fn encoded_byte(&self) -> Result<u8> { 60 let recovery_id = self.recovery_id.to_byte(); 61 match self.chain_id { 62 // Standard 63 None => Ok(recovery_id), 64 // ETH legacy 65 Some(0) => Ok(recovery_id.saturating_add(Self::ETH_LEGACY_OFFSET)), // 27/28 66 // EIP-155 67 Some(chain_id) => { 68 let recovery_id = (recovery_id as u64) 69 .saturating_add(chain_id.saturating_mul(2)) 70 .saturating_add(Self::ETH_EIP155_OFFSET as u64); 71 Ok(u8::try_from(recovery_id)?) 72 } 73 } 74 } 75 } 76 77 impl ToBytes for RecoveryID { 78 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> { 79 let encoded_byte = self.encoded_byte().map_err(error)?; 80 encoded_byte.write_le(&mut writer) 81 } 82 } 83 84 impl FromBytes for RecoveryID { 85 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> { 86 // Read the recovery ID byte. 87 let recovery_id_byte = u8::read_le(&mut reader)?; 88 89 // Determine if the recovery ID follows the Ethereum convention. 90 let (recovery_id_without_offset, chain_id) = match recovery_id_byte { 91 27 | 28 => (recovery_id_byte.saturating_sub(Self::ETH_LEGACY_OFFSET), Some(0u64)), 92 v if v >= Self::ETH_EIP155_OFFSET => { 93 let y = (v.saturating_sub(Self::ETH_EIP155_OFFSET)) % 2; 94 let id = (v.saturating_sub(Self::ETH_EIP155_OFFSET)) / 2; 95 (y, Some(id as u64)) 96 } 97 _ => (recovery_id_byte, None), 98 }; 99 100 // Construct the recovery ID from the byte. 101 let recovery_id = ECDSARecoveryId::from_byte(recovery_id_without_offset) 102 .ok_or_else(|| error(format!("Invalid recovery ID byte {recovery_id_byte}")))?; 103 Ok(Self { recovery_id, chain_id }) 104 } 105 } 106 107 /// An ECDSA/Secp256k1 signature (r,s) signature along with the recovery ID. 108 #[derive(Clone, PartialEq, Eq)] 109 pub struct ECDSASignature { 110 /// The (r,s) signature. 111 pub signature: Signature, 112 /// The recovery ID. 113 pub recovery_id: RecoveryID, 114 } 115 116 impl ECDSASignature { 117 /// The base signature size in bytes for secp256k1. 118 pub const BASE_SIGNATURE_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE * 2; 119 /// The size of an Ethereum address in bytes. 120 pub const ETHEREUM_ADDRESS_SIZE_IN_BYTES: usize = 20; 121 /// The prehash size in bytes for secp256k1. 122 pub const PREHASH_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE; 123 /// The ECDSA Signature size in bits for secp256k1 (including the one-byte recovery ID). 124 pub const SIGNATURE_SIZE_IN_BYTES: usize = Self::BASE_SIGNATURE_SIZE_IN_BYTES + 1; 125 /// The compressed VerifyingKey size in bytes for secp256k1 (32 byte field + one-byte header). 126 pub const VERIFYING_KEY_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE + 1; 127 128 /// Returns the recovery ID. 129 pub const fn recovery_id(&self) -> ECDSARecoveryId { 130 self.recovery_id.recovery_id 131 } 132 133 /// Returns a signature on a `message` using the given `signing_key` and hash function. 134 pub fn sign<H: Hash<Output = Vec<bool>>>( 135 signing_key: &SigningKey, 136 hasher: &H, 137 message: &[H::Input], 138 ) -> Result<Self> { 139 // Hash the message. 140 let hash_bits = hasher.hash(message)?; 141 // Convert the hash output to bytes. 142 let hash_bytes = bytes_from_bits_le(&hash_bits); 143 144 // Sign the prehashed message. 145 signing_key 146 .sign_prehash(&hash_bytes) 147 .map(|(signature, recovery_id)| { 148 let recovery_id = RecoveryID { recovery_id, chain_id: None }; 149 Self { signature, recovery_id } 150 }) 151 .map_err(|e| anyhow!("Failed to sign message: {e:?}")) 152 } 153 154 /// Recover the public key from `(r,s, recovery_id)` using *your* hasher on `message`. 155 pub fn recover_public_key<H: Hash<Output = Vec<bool>>>( 156 &self, 157 hasher: &H, 158 message: &[H::Input], 159 ) -> Result<VerifyingKey> { 160 // Hash the message. 161 let hash_bits = hasher.hash(message)?; 162 163 // Recover the public key using the prehash. 164 self.recover_public_key_with_digest(&hash_bits) 165 } 166 167 /// Recover the public key from `(r,s, recovery_id)` using *your* hasher on `message`. 168 pub fn recover_public_key_with_digest(&self, digest_bits: &[bool]) -> Result<VerifyingKey> { 169 // Convert the digest output to bytes. 170 let digest = bytes_from_bits_le(digest_bits); 171 172 // Recover the public key using the prehash. 173 VerifyingKey::recover_from_prehash(&digest, &self.signature, self.recovery_id()) 174 .map_err(|e| anyhow!("Failed to recover public key: {e:?}")) 175 } 176 177 /// Verify `(r,s)` against `verifying_key` using *your* hasher on `message`. 178 pub fn verify<H: Hash<Output = Vec<bool>>>( 179 &self, 180 verifying_key: &VerifyingKey, 181 hasher: &H, 182 message: &[H::Input], 183 ) -> Result<()> { 184 // Hash the message. 185 let hash_bits = hasher.hash(message)?; 186 187 // Verify the signature using the prehash. 188 self.verify_with_digest(verifying_key, &hash_bits) 189 } 190 191 /// Verify `(r,s)` against `verifying_key` using the provided `digest`. 192 pub fn verify_with_digest(&self, verifying_key: &VerifyingKey, digest_bits: &[bool]) -> Result<()> { 193 // Convert the digest output to bytes. 194 let digest = bytes_from_bits_le(digest_bits); 195 196 // Verify the signature using the prehash digest. 197 verifying_key.verify_prehash(&digest, &self.signature).map_err(|e| anyhow!("Failed to verify signature: {e:?}")) 198 } 199 200 /// Verify `(r,s)` against `verifying_key` using *your* hasher on `message`. 201 pub fn verify_ethereum<H: Hash<Output = Vec<bool>>>( 202 &self, 203 ethereum_address: &[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES], 204 hasher: &H, 205 message: &[H::Input], 206 ) -> Result<()> { 207 // Hash the message. 208 let hash_bits = hasher.hash(message)?; 209 210 // Ensure that the derived Ethereum address matches the provided one. 211 self.verify_ethereum_with_digest(ethereum_address, &hash_bits) 212 } 213 214 /// Verify `(r,s)` against `verifying_key` using *your* hasher on `message`. 215 pub fn verify_ethereum_with_digest( 216 &self, 217 ethereum_address: &[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES], 218 digest_bits: &[bool], 219 ) -> Result<()> { 220 // Derive the verifying key from the signature. 221 let verifying_key = self.recover_public_key_with_digest(digest_bits)?; 222 223 // Ensure that the derived Ethereum address matches the provided one. 224 let derived_ethereum_address = Self::ethereum_address_from_public_key(&verifying_key)?; 225 ensure!( 226 &derived_ethereum_address == ethereum_address, 227 "Derived Ethereum address does not match the provided address." 228 ); 229 230 Ok(()) 231 } 232 233 /// Converts a VerifyingKey to an Ethereum address (20 bytes). 234 pub fn ethereum_address_from_public_key( 235 verifying_key: &VerifyingKey, 236 ) -> Result<[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES]> { 237 // Get the uncompressed public key bytes as [0x04, x_bytes..., y_bytes...] 238 let public_key_point = verifying_key.to_encoded_point(false); 239 let public_key_bytes = public_key_point.as_bytes(); 240 241 // Skip the 0x04 prefix, keep only the x and y coordinates (64 bytes) 242 let coordinates_only = &public_key_bytes[1..]; // 32 bytes x + 32 bytes y 243 244 // Step 3: Hash the coordinates with Keccak256 245 let address_hash = Keccak256::default().hash(&coordinates_only.to_bits_le())?; 246 let address_bytes = bytes_from_bits_le(&address_hash); 247 248 // Step 4: Take the last 20 bytes as the Ethereum address 249 let mut ethereum_address = [0u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES]; 250 ethereum_address.copy_from_slice(&address_bytes[12..32]); 251 252 Ok(ethereum_address) 253 } 254 255 /// Parses a verifying key from bytes. 256 pub fn verifying_key_from_bytes(bytes: &[u8]) -> Result<VerifyingKey> { 257 VerifyingKey::from_sec1_bytes(bytes).map_err(|e| anyhow!("Failed to parse verifying key: {e:?}")) 258 } 259 } 260 261 impl ToBytes for ECDSASignature { 262 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> { 263 // Write the signature bytes. 264 self.signature.to_bytes().to_vec().write_le(&mut writer)?; 265 // Write the recovery ID. 266 self.recovery_id.write_le(&mut writer) 267 } 268 } 269 270 impl FromBytes for ECDSASignature { 271 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> { 272 // Read the signature bytes. 273 let mut bytes = vec![0u8; Self::BASE_SIGNATURE_SIZE_IN_BYTES]; 274 reader.read_exact(&mut bytes)?; 275 // Construct the signature from the bytes. 276 let signature = Signature::from_slice(&bytes).map_err(error)?; 277 278 // Read the recovery ID 279 let recovery_id = RecoveryID::read_le(&mut reader)?; 280 281 Ok(Self { signature, recovery_id }) 282 } 283 } 284 285 impl FromStr for ECDSASignature { 286 type Err = Error; 287 288 /// Parses a hex-encoded string into an ECDSASignature. 289 fn from_str(signature: &str) -> Result<Self, Self::Err> { 290 let mut s = signature.trim(); 291 292 // Accept optional 0x prefix 293 if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { 294 s = rest; 295 } 296 297 // Decode the hex string into bytes. 298 let bytes = hex::decode(s)?; 299 300 // Construct the signature from the bytes. 301 Self::from_bytes_le(&bytes) 302 } 303 } 304 305 impl Debug for ECDSASignature { 306 fn fmt(&self, f: &mut Formatter) -> fmt::Result { 307 Display::fmt(self, f) 308 } 309 } 310 311 impl Display for ECDSASignature { 312 /// Writes the signature as a hex string. 313 fn fmt(&self, f: &mut Formatter) -> fmt::Result { 314 write!(f, "{}", hex::encode(self.to_bytes_le().map_err(|_| fmt::Error)?)) 315 } 316 } 317 318 #[cfg(test)] 319 mod test_helpers { 320 use super::*; 321 322 pub(crate) type DefaultHasher = Keccak256; 323 324 /// Samples a random ecdsa signature. 325 pub(super) fn sample_ecdsa_signature<H: Hash<Output = Vec<bool>, Input = bool>>( 326 num_bytes: usize, 327 hasher: &H, 328 rng: &mut TestRng, 329 ) -> (SigningKey, Vec<u8>, ECDSASignature) { 330 // Sample a random signing key. 331 let signing_key = SigningKey::random(rng); 332 333 // Sample a random message. 334 let message: Vec<u8> = (0..num_bytes).map(|_| rng.r#gen()).collect::<Vec<_>>(); 335 336 // Sign the message. 337 let signature = ECDSASignature::sign::<H>(&signing_key, hasher, &message.to_bits_le()).unwrap(); 338 339 // Return the signing key, message, and signature. 340 (signing_key, message, signature) 341 } 342 }