/ console / algorithms / src / ecdsa / mod.rs
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  }