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