fix(core): make Signature.v a u64 instead of a u8 and expose `verify` (#21)

u8 would not work if a large chain_id was used
This commit is contained in:
Georgios Konstantopoulos 2020-06-17 09:45:15 +03:00 committed by GitHub
parent 1a47e933ae
commit 2c734f0d61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 17 deletions

View File

@ -165,7 +165,7 @@ impl PrivateKey {
let r = H256::from_slice(&signature.r.b32()); let r = H256::from_slice(&signature.r.b32());
let s = H256::from_slice(&signature.s.b32()); let s = H256::from_slice(&signature.s.b32());
Signature { v: v as u8, r, s } Signature { v, r, s }
} }
} }

View File

@ -4,13 +4,14 @@ use crate::{
utils::hash_message, utils::hash_message,
}; };
use rustc_hex::ToHex; use rustc_hex::{FromHex, ToHex};
use secp256k1::{ use secp256k1::{
self as Secp256k1, Error as Secp256k1Error, Message, RecoveryId, self as Secp256k1, Error as Secp256k1Error, Message, RecoveryId,
Signature as RecoverableSignature, Signature as RecoverableSignature,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt}; use std::{convert::TryFrom, fmt, str::FromStr};
use thiserror::Error; use thiserror::Error;
/// An error involving a signature. /// An error involving a signature.
@ -22,6 +23,13 @@ pub enum SignatureError {
/// Invalid length, secp256k1 signatures are 65 bytes /// Invalid length, secp256k1 signatures are 65 bytes
#[error("invalid signature length, got {0}, expected 65")] #[error("invalid signature length, got {0}, expected 65")]
InvalidLength(usize), InvalidLength(usize),
/// When parsing a signature from string to hex
#[error(transparent)]
DecodingError(#[from] rustc_hex::FromHexError),
/// Thrown when signature verification failed (i.e. when the address that
/// produced the signature did not match the expected address)
#[error("Signature verification failed. Expected {0}, got {0}")]
VerificationError(Address, Address),
} }
/// Recovery message data. /// Recovery message data.
@ -45,7 +53,7 @@ pub struct Signature {
/// S Value /// S Value
pub s: H256, pub s: H256,
/// V value in 'Electrum' notation. /// V value in 'Electrum' notation.
pub v: u8, pub v: u64,
} }
impl fmt::Display for Signature { impl fmt::Display for Signature {
@ -56,6 +64,21 @@ impl fmt::Display for Signature {
} }
impl Signature { impl Signature {
/// Verifies that signature on `message` was produced by `address`
pub fn verify<M, A>(&self, message: M, address: A) -> Result<(), SignatureError>
where
M: Into<RecoveryMessage>,
A: Into<Address>,
{
let address = address.into();
let recovered = self.recover(message)?;
if recovered != address {
return Err(SignatureError::VerificationError(address, recovered));
}
Ok(())
}
/// Recovers the Ethereum address which was used to sign the given message. /// Recovers the Ethereum address which was used to sign the given message.
/// ///
/// Recovery signature data uses 'Electrum' notation, this means the `v` /// Recovery signature data uses 'Electrum' notation, this means the `v`
@ -92,13 +115,7 @@ impl Signature {
/// Retrieve the recovery ID. /// Retrieve the recovery ID.
fn recovery_id(&self) -> Result<RecoveryId, SignatureError> { fn recovery_id(&self) -> Result<RecoveryId, SignatureError> {
let standard_v = match self.v { let standard_v = normalize_recovery_id(self.v);
27 => 0,
28 => 1,
v if v >= 35 => ((v - 1) % 2) as _,
_ => 4,
};
Ok(RecoveryId::parse(standard_v)?) Ok(RecoveryId::parse(standard_v)?)
} }
@ -108,6 +125,17 @@ impl Signature {
} }
} }
fn normalize_recovery_id(v: u64) -> u8 {
match v {
0 => 0,
1 => 1,
27 => 0,
28 => 1,
v if v >= 35 => ((v - 1) % 2) as _,
_ => 4,
}
}
impl<'a> TryFrom<&'a [u8]> for Signature { impl<'a> TryFrom<&'a [u8]> for Signature {
type Error = SignatureError; type Error = SignatureError;
@ -123,7 +151,16 @@ impl<'a> TryFrom<&'a [u8]> for Signature {
let r = H256::from_slice(&bytes[0..32]); let r = H256::from_slice(&bytes[0..32]);
let s = H256::from_slice(&bytes[32..64]); let s = H256::from_slice(&bytes[32..64]);
Ok(Signature { r, s, v }) Ok(Signature { r, s, v: v.into() })
}
}
impl FromStr for Signature {
type Err = SignatureError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.from_hex::<Vec<u8>>()?;
Signature::try_from(&bytes[..])
} }
} }
@ -132,7 +169,9 @@ impl From<&Signature> for [u8; 65] {
let mut sig = [0u8; 65]; let mut sig = [0u8; 65];
sig[..32].copy_from_slice(src.r.as_bytes()); sig[..32].copy_from_slice(src.r.as_bytes());
sig[32..64].copy_from_slice(src.s.as_bytes()); sig[32..64].copy_from_slice(src.s.as_bytes());
sig[64] = src.v; // TODO: What if we try to serialize a signature where
// the `v` is not normalized?
sig[64] = src.v as u8;
sig sig
} }
} }
@ -212,6 +251,9 @@ mod tests {
// if provided with a hash, it will skip hashing // if provided with a hash, it will skip hashing
let recovered2 = signature.recover(hash).unwrap(); let recovered2 = signature.recover(hash).unwrap();
// verifies the signature is produced by `address`
signature.verify(message, address).unwrap();
assert_eq!(recovered, address); assert_eq!(recovered, address);
assert_eq!(recovered2, address); assert_eq!(recovered2, address);
} }

View File

@ -8,10 +8,8 @@ fn main() {
let signature = wallet.sign_message(message); let signature = wallet.sign_message(message);
println!("Produced signature {}", signature); println!("Produced signature {}", signature);
// recover the address that signed it // verify the signature
let recovered = signature.recover(message).unwrap(); signature.verify(message, wallet.address()).unwrap();
assert_eq!(recovered, wallet.address());
println!("Verified signature produced by {:?}!", wallet.address()); println!("Verified signature produced by {:?}!", wallet.address());
} }