#[cfg(test)]
pub mod tests;
mod serialize;
use super::*;
use snarkvm_utilities::bytes_from_bits_le;
use k256::{
Secp256k1,
ecdsa::{
RecoveryId as ECDSARecoveryId,
Signature,
SigningKey,
VerifyingKey,
signature::hazmat::{PrehashSigner, PrehashVerifier},
},
elliptic_curve::{Curve, generic_array::typenum::Unsigned},
};
#[derive(Clone, PartialEq, Eq)]
pub struct RecoveryID {
pub recovery_id: ECDSARecoveryId,
pub chain_id: Option<u64>,
}
impl RecoveryID {
const ETH_EIP155_OFFSET: u8 = 35;
const ETH_LEGACY_OFFSET: u8 = 27;
#[inline]
fn encoded_byte(&self) -> Result<u8> {
let recovery_id = self.recovery_id.to_byte();
match self.chain_id {
None => Ok(recovery_id),
Some(0) => Ok(recovery_id.saturating_add(Self::ETH_LEGACY_OFFSET)), Some(chain_id) => {
let recovery_id = (recovery_id as u64)
.saturating_add(chain_id.saturating_mul(2))
.saturating_add(Self::ETH_EIP155_OFFSET as u64);
Ok(u8::try_from(recovery_id)?)
}
}
}
}
impl ToBytes for RecoveryID {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
let encoded_byte = self.encoded_byte().map_err(error)?;
encoded_byte.write_le(&mut writer)
}
}
impl FromBytes for RecoveryID {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let recovery_id_byte = u8::read_le(&mut reader)?;
let (recovery_id_without_offset, chain_id) = match recovery_id_byte {
27 | 28 => (recovery_id_byte.saturating_sub(Self::ETH_LEGACY_OFFSET), Some(0u64)),
v if v >= Self::ETH_EIP155_OFFSET => {
let y = (v.saturating_sub(Self::ETH_EIP155_OFFSET)) % 2;
let id = (v.saturating_sub(Self::ETH_EIP155_OFFSET)) / 2;
(y, Some(id as u64))
}
_ => (recovery_id_byte, None),
};
let recovery_id = ECDSARecoveryId::from_byte(recovery_id_without_offset)
.ok_or_else(|| error(format!("Invalid recovery ID byte {recovery_id_byte}")))?;
Ok(Self { recovery_id, chain_id })
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct ECDSASignature {
pub signature: Signature,
pub recovery_id: RecoveryID,
}
impl ECDSASignature {
pub const BASE_SIGNATURE_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE * 2;
pub const ETHEREUM_ADDRESS_SIZE_IN_BYTES: usize = 20;
pub const PREHASH_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE;
pub const SIGNATURE_SIZE_IN_BYTES: usize = Self::BASE_SIGNATURE_SIZE_IN_BYTES + 1;
pub const VERIFYING_KEY_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE + 1;
pub const fn recovery_id(&self) -> ECDSARecoveryId {
self.recovery_id.recovery_id
}
pub fn sign<H: Hash<Output = Vec<bool>>>(
signing_key: &SigningKey,
hasher: &H,
message: &[H::Input],
) -> Result<Self> {
let hash_bits = hasher.hash(message)?;
let hash_bytes = bytes_from_bits_le(&hash_bits);
signing_key
.sign_prehash(&hash_bytes)
.map(|(signature, recovery_id)| {
let recovery_id = RecoveryID { recovery_id, chain_id: None };
Self { signature, recovery_id }
})
.map_err(|e| anyhow!("Failed to sign message: {e:?}"))
}
pub fn recover_public_key<H: Hash<Output = Vec<bool>>>(
&self,
hasher: &H,
message: &[H::Input],
) -> Result<VerifyingKey> {
let hash_bits = hasher.hash(message)?;
self.recover_public_key_with_digest(&hash_bits)
}
pub fn recover_public_key_with_digest(&self, digest_bits: &[bool]) -> Result<VerifyingKey> {
let digest = bytes_from_bits_le(digest_bits);
VerifyingKey::recover_from_prehash(&digest, &self.signature, self.recovery_id())
.map_err(|e| anyhow!("Failed to recover public key: {e:?}"))
}
pub fn verify<H: Hash<Output = Vec<bool>>>(
&self,
verifying_key: &VerifyingKey,
hasher: &H,
message: &[H::Input],
) -> Result<()> {
let hash_bits = hasher.hash(message)?;
self.verify_with_digest(verifying_key, &hash_bits)
}
pub fn verify_with_digest(&self, verifying_key: &VerifyingKey, digest_bits: &[bool]) -> Result<()> {
let digest = bytes_from_bits_le(digest_bits);
verifying_key.verify_prehash(&digest, &self.signature).map_err(|e| anyhow!("Failed to verify signature: {e:?}"))
}
pub fn verify_ethereum<H: Hash<Output = Vec<bool>>>(
&self,
ethereum_address: &[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES],
hasher: &H,
message: &[H::Input],
) -> Result<()> {
let hash_bits = hasher.hash(message)?;
self.verify_ethereum_with_digest(ethereum_address, &hash_bits)
}
pub fn verify_ethereum_with_digest(
&self,
ethereum_address: &[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES],
digest_bits: &[bool],
) -> Result<()> {
let verifying_key = self.recover_public_key_with_digest(digest_bits)?;
let derived_ethereum_address = Self::ethereum_address_from_public_key(&verifying_key)?;
ensure!(
&derived_ethereum_address == ethereum_address,
"Derived Ethereum address does not match the provided address."
);
Ok(())
}
pub fn ethereum_address_from_public_key(
verifying_key: &VerifyingKey,
) -> Result<[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES]> {
let public_key_point = verifying_key.to_encoded_point(false);
let public_key_bytes = public_key_point.as_bytes();
let coordinates_only = &public_key_bytes[1..];
let address_hash = Keccak256::default().hash(&coordinates_only.to_bits_le())?;
let address_bytes = bytes_from_bits_le(&address_hash);
let mut ethereum_address = [0u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES];
ethereum_address.copy_from_slice(&address_bytes[12..32]);
Ok(ethereum_address)
}
pub fn verifying_key_from_bytes(bytes: &[u8]) -> Result<VerifyingKey> {
VerifyingKey::from_sec1_bytes(bytes).map_err(|e| anyhow!("Failed to parse verifying key: {e:?}"))
}
}
impl ToBytes for ECDSASignature {
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
self.signature.to_bytes().to_vec().write_le(&mut writer)?;
self.recovery_id.write_le(&mut writer)
}
}
impl FromBytes for ECDSASignature {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
let mut bytes = vec![0u8; Self::BASE_SIGNATURE_SIZE_IN_BYTES];
reader.read_exact(&mut bytes)?;
let signature = Signature::from_slice(&bytes).map_err(error)?;
let recovery_id = RecoveryID::read_le(&mut reader)?;
Ok(Self { signature, recovery_id })
}
}
impl FromStr for ECDSASignature {
type Err = Error;
fn from_str(signature: &str) -> Result<Self, Self::Err> {
let mut s = signature.trim();
if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
s = rest;
}
let bytes = hex::decode(s)?;
Self::from_bytes_le(&bytes)
}
}
impl Debug for ECDSASignature {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl Display for ECDSASignature {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", hex::encode(self.to_bytes_le().map_err(|_| fmt::Error)?))
}
}
#[cfg(test)]
mod test_helpers {
use super::*;
pub(crate) type DefaultHasher = Keccak256;
pub(super) fn sample_ecdsa_signature<H: Hash<Output = Vec<bool>, Input = bool>>(
num_bytes: usize,
hasher: &H,
rng: &mut TestRng,
) -> (SigningKey, Vec<u8>, ECDSASignature) {
let signing_key = SigningKey::random(rng);
let message: Vec<u8> = (0..num_bytes).map(|_| rng.r#gen()).collect::<Vec<_>>();
let signature = ECDSASignature::sign::<H>(&signing_key, hasher, &message.to_bits_le()).unwrap();
(signing_key, message, signature)
}
}