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:
parent
1a47e933ae
commit
2c734f0d61
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue