#[cfg(feature = "sign")]
use std::fmt;
use std::future::Future;
#[cfg(feature = "sign")]
use k256::ecdsa::{RecoveryId, Signature as CurveSignature, SigningKey, VerifyingKey};
use serde::{Serialize, Serializer, ser::SerializeStruct};
#[cfg(feature = "sign")]
use zeroize::{ZeroizeOnDrop, Zeroizing};
use crate::utils::address::Address;
#[cfg(feature = "sign")]
use crate::utils::hex;
use crate::utils::json::Json;
use crate::utils::sign::eip712::{Domain, TypedStruct};
#[cfg(feature = "sign")]
use crate::utils::sign::keccak::keccak256;
const V_OFFSET: u8 = 27;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Signature {
r: [u8; 32],
s: [u8; 32],
v: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum SignError {
#[error("the key could not sign this digest")]
Sign,
#[error("a signature is 65 bytes of hex")]
NotASignature,
#[error("the signature components are not a valid curve point")]
Malformed,
#[error("no public key recovers from this signature")]
Unrecoverable,
#[error("`{0}` is not an Ethereum recovery parameter; expected 27 or 28")]
BadV(u8),
#[error("the signature is from {recovered}, not {expected}")]
WrongSigner { expected: Address, recovered: Address },
#[error("the key is not a valid secp256k1 private key")]
NotAKey,
}
impl Signature {
#[must_use]
pub const fn from_parts(r: [u8; 32], s: [u8; 32], v: u8) -> Self {
Signature { r, s, v }
}
#[cfg(feature = "sign")]
pub fn from_hex(text: &str) -> Result<Signature, SignError> {
let bytes: [u8; 65] = hex::decode_array(text).map_err(|_| SignError::NotASignature)?;
let mut r = [0u8; 32];
let mut s = [0u8; 32];
r.copy_from_slice(&bytes[..32]);
s.copy_from_slice(&bytes[32..64]);
let parity = match bytes[64] {
0 | 27 => 0,
1 | 28 => 1,
other => return Err(SignError::BadV(other)),
};
let compact = Signature { r, s, v: V_OFFSET + parity }.to_compact();
let curve = CurveSignature::from_slice(&compact).map_err(|_| SignError::Malformed)?;
Ok(match curve.normalize_s() {
None => Signature { r, s, v: V_OFFSET + parity },
Some(low) => {
s.copy_from_slice(&low.to_bytes()[32..]);
Signature { r, s, v: V_OFFSET + (parity ^ 1) }
}
})
}
#[must_use]
pub const fn r(&self) -> &[u8; 32] {
&self.r
}
#[must_use]
pub const fn s(&self) -> &[u8; 32] {
&self.s
}
#[must_use]
pub const fn v(&self) -> u8 {
self.v
}
#[must_use]
pub const fn recovery_id(&self) -> Option<u8> {
self.v.checked_sub(V_OFFSET)
}
#[must_use]
pub fn r_hex(&self) -> String {
crate::utils::hex::encode_prefixed(&self.r)
}
#[must_use]
pub fn s_hex(&self) -> String {
crate::utils::hex::encode_prefixed(&self.s)
}
#[must_use]
pub fn to_compact(&self) -> [u8; 64] {
let mut bytes = [0u8; 64];
bytes[..32].copy_from_slice(&self.r);
bytes[32..].copy_from_slice(&self.s);
bytes
}
#[must_use]
pub fn to_json(&self) -> String {
self.json().compact()
}
pub(crate) fn json(&self) -> Json {
Json::Object(vec![
("r".to_owned(), Json::String(self.r_hex())),
("s".to_owned(), Json::String(self.s_hex())),
("v".to_owned(), Json::Number(self.v.to_string())),
])
}
}
impl Serialize for Signature {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut signature = serializer.serialize_struct("Signature", 3)?;
signature.serialize_field("r", &self.r_hex())?;
signature.serialize_field("s", &self.s_hex())?;
signature.serialize_field("v", &self.v)?;
signature.end()
}
}
#[cfg(feature = "sign")]
pub fn recover(digest: &[u8; 32], signature: &Signature) -> Result<Address, SignError> {
let recovery_id = signature.recovery_id().and_then(RecoveryId::from_byte).ok_or(SignError::BadV(signature.v))?;
let curve_signature = CurveSignature::from_slice(&signature.to_compact()).map_err(|_| SignError::Malformed)?;
let key = VerifyingKey::recover_from_prehash(digest, &curve_signature, recovery_id)
.map_err(|_| SignError::Unrecoverable)?;
Ok(address_of_public_key(&key))
}
#[cfg(feature = "sign")]
pub fn verify(digest: &[u8; 32], signature: &Signature, expected: Address) -> Result<(), SignError> {
let recovered = recover(digest, signature)?;
match recovered == expected {
true => Ok(()),
false => Err(SignError::WrongSigner { expected, recovered }),
}
}
#[cfg(feature = "sign")]
#[must_use]
pub fn address_of_public_key(key: &VerifyingKey) -> Address {
let point = key.to_encoded_point(false);
let coordinates = &point.as_bytes()[1..];
let hash = keccak256(coordinates);
let mut bytes = [0u8; 20];
bytes.copy_from_slice(&hash[12..]);
Address::from_bytes(bytes)
}
pub trait Signer {
fn address(&self) -> Address;
fn sign_typed(&self, domain: &Domain, data: &TypedStruct<'_>)
-> impl Future<Output = Result<Signature, SignError>>;
}
#[cfg(feature = "sign")]
const _: fn() = || {
fn wiped_on_drop<T: ZeroizeOnDrop>() {}
wiped_on_drop::<SigningKey>();
};
#[cfg(feature = "sign")]
pub struct LocalKey {
key: SigningKey,
address: Address,
}
#[cfg(feature = "sign")]
impl LocalKey {
pub fn from_bytes(secret: &[u8; 32]) -> Result<Self, SignError> {
let key = SigningKey::from_bytes(secret.into()).map_err(|_| SignError::NotAKey)?;
let address = address_of_public_key(key.verifying_key());
Ok(LocalKey { key, address })
}
pub fn from_hex(secret: &str) -> Result<Self, SignError> {
let mut bytes = Zeroizing::new([0u8; 32]);
hex::decode_into(secret, bytes.as_mut_slice()).map_err(|_| SignError::NotAKey)?;
LocalKey::from_bytes(&bytes)
}
#[must_use]
pub const fn address(&self) -> Address {
self.address
}
pub fn sign_digest(&self, digest: &[u8; 32]) -> Result<Signature, SignError> {
let (signature, recovery_id): (CurveSignature, RecoveryId) =
self.key.sign_prehash_recoverable(digest).map_err(|_| SignError::Sign)?;
let bytes = signature.to_bytes();
let mut r = [0u8; 32];
let mut s = [0u8; 32];
r.copy_from_slice(&bytes[..32]);
s.copy_from_slice(&bytes[32..]);
Ok(Signature { r, s, v: recovery_id.to_byte() + V_OFFSET })
}
}
#[cfg(feature = "sign")]
impl Signer for LocalKey {
fn address(&self) -> Address {
self.address
}
fn sign_typed(
&self,
domain: &Domain,
data: &TypedStruct<'_>,
) -> impl Future<Output = Result<Signature, SignError>> {
std::future::ready(self.sign_digest(&data.digest(domain)))
}
}
#[cfg(feature = "sign")]
impl fmt::Debug for LocalKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LocalKey").field("address", &self.address.to_hex()).finish_non_exhaustive()
}
}
#[cfg(feature = "sign")]
impl ZeroizeOnDrop for LocalKey {}
#[cfg(test)]
#[cfg(feature = "sign")]
#[path = "tests/signer.rs"]
mod tests;