Skip to content

Commit

Permalink
change subxt compat impl
Browse files Browse the repository at this point in the history
  • Loading branch information
pkhry committed Jun 17, 2024
1 parent 3eb58e8 commit bc24b52
Showing 1 changed file with 119 additions and 118 deletions.
237 changes: 119 additions & 118 deletions signer/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ const SECRET_KEY_LENGTH: usize = 32;
/// Bytes representing a private key.
pub type SecretKeyBytes = [u8; SECRET_KEY_LENGTH];

/// The public key for an [`Keypair`] key pair. This is the uncompressed variant of [`ecdsa::PublicKey`].
pub struct PublicKey(pub [u8; 65]);

impl AsRef<[u8]> for PublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

/// An ethereum keypair implementation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Keypair(ecdsa::Keypair);
Expand All @@ -25,6 +34,85 @@ impl From<ecdsa::Keypair> for Keypair {
Self(kp)
}
}

impl Keypair {
/// Create a keypair from a BIP-39 mnemonic phrase, optional password, account index, and
/// derivation type.
///
/// **Note:** if the `std` feature is not enabled, we won't attempt to normalize the provided password
/// to NFKD first, and so this is your responsibility. This is not a concern if only ASCII
/// characters are used in the password.
///
/// # Example
///
/// ```rust
/// use subxt_signer::{ bip39::Mnemonic, eth::{ Keypair, DerivationPath } };
///
/// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// let mnemonic = Mnemonic::parse(phrase).unwrap();
/// let keypair = Keypair::from_phrase(&mnemonic, None, DerivationPath::eth(0,0)).unwrap();
///
/// keypair.sign(b"Hello world!");
/// ```
pub fn from_phrase(
mnemonic: &bip39::Mnemonic,
password: Option<&str>,
derivation_path: DerivationPath,
) -> Result<Self, Error> {
// `to_seed` isn't available unless std is enabled in bip39.
#[cfg(feature = "std")]
let seed = mnemonic.to_seed(password.unwrap_or(""));
#[cfg(not(feature = "std"))]
let seed = mnemonic.to_seed_normalized(password.unwrap_or(""));

// TODO: Currently, we use bip32 to derive private keys which under the hood uses
// the Rust k256 crate. We _also_ use the secp256k1 crate (which is very similar).
// It'd be great if we could 100% use just one of the two crypto libs. bip32 has
// a feature flag to use secp256k1, but it's unfortunately a different version (older)
// than ours.
let private = bip32::XPrv::derive_from_path(seed, &derivation_path.inner)
.map_err(|_| Error::DeriveFromPath)?;

Keypair::from_secret_key(private.to_bytes())
}

/// Turn a 16, 32 or 64 byte seed into a keypair.
///
/// # Warning
///
/// This will only be secure if the seed is secure!
pub fn from_seed(seed: &[u8]) -> Result<Self, Error> {
let private = bip32::XPrv::new(seed).map_err(|_| Error::InvalidSeed)?;
Keypair::from_secret_key(private.to_bytes())
}

/// Turn a 32 byte secret key into a keypair.
///
/// # Warning
///
/// This will only be secure if the secret key is secure!
pub fn from_secret_key(secret_key: SecretKeyBytes) -> Result<Self, Error> {
ecdsa::Keypair::from_secret_key(secret_key)
.map(Self)
.map_err(|_| Error::InvalidSeed)
}

/// Obtain the [`eth::PublicKey`] of this keypair.
pub fn public_key(&self) -> PublicKey {
let uncompressed = self.0 .0.public_key().serialize_uncompressed();
PublicKey(uncompressed)
}

/// Signs an arbitrary message payload.
pub fn sign(&self, signer_payload: &[u8]) -> Signature {
self.sign_prehashed(&keccak(signer_payload).0)
}

/// Signs a pre-hashed message.
pub fn sign_prehashed(&self, message_hash: &[u8; 32]) -> Signature {
Signature(self.0.sign_prehashed(message_hash).0)
}
}
/// A derivation path. This can be parsed from a valid derivation path string like
/// `"m/44'/60'/0'/0/0"`, or we can construct one using the helpers [`DerivationPath::empty()`]
/// and [`DerivationPath::eth()`].
Expand Down Expand Up @@ -79,12 +167,14 @@ impl AsRef<[u8; 65]> for Signature {
}
}

pub fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &ecdsa::PublicKey) -> bool {
pub fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &PublicKey) -> bool {
let message_hash = keccak(message.as_ref());
let wrapped =
Message::from_digest_slice(message_hash.as_bytes()).expect("Message is 32 bytes; qed");

ecdsa::internal::verify(&sig.0, &wrapped, pubkey)
let pubkey = secp256k1::PublicKey::from_slice(&pubkey.0).expect("test");

ecdsa::internal::verify(&sig.0, &wrapped, &ecdsa::PublicKey(pubkey.serialize()))
}

/// An error handed back if creating a keypair fails.
Expand Down Expand Up @@ -151,143 +241,54 @@ pub mod dev {
#[cfg(feature = "subxt")]
mod subxt_compat {
use super::*;
use crate::ecdsa;
use subxt_core::config::Config;
use subxt_core::tx::signer::Signer as SignerT;
use subxt_core::utils::AccountId20;
use subxt_core::utils::MultiAddress;

impl Keypair {
/// Create a keypair from a BIP-39 mnemonic phrase, optional password, account index, and
/// derivation type.
///
/// **Note:** if the `std` feature is not enabled, we won't attempt to normalize the provided password
/// to NFKD first, and so this is your responsibility. This is not a concern if only ASCII
/// characters are used in the password.
///
/// # Example
///
/// ```rust
/// use subxt_signer::{ bip39::Mnemonic, eth::{ Keypair, DerivationPath } };
///
/// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
/// let mnemonic = Mnemonic::parse(phrase).unwrap();
/// let keypair = Keypair::from_phrase(&mnemonic, None, DerivationPath::eth(0,0)).unwrap();
///
/// keypair.sign(b"Hello world!");
/// ```
pub fn from_phrase(
mnemonic: &bip39::Mnemonic,
password: Option<&str>,
derivation_path: DerivationPath,
) -> Result<Self, Error> {
// `to_seed` isn't available unless std is enabled in bip39.
#[cfg(feature = "std")]
let seed = mnemonic.to_seed(password.unwrap_or(""));
#[cfg(not(feature = "std"))]
let seed = mnemonic.to_seed_normalized(password.unwrap_or(""));

// TODO: Currently, we use bip32 to derive private keys which under the hood uses
// the Rust k256 crate. We _also_ use the secp256k1 crate (which is very similar).
// It'd be great if we could 100% use just one of the two crypto libs. bip32 has
// a feature flag to use secp256k1, but it's unfortunately a different version (older)
// than ours.
let private = bip32::XPrv::derive_from_path(seed, &derivation_path.inner)
.map_err(|_| Error::DeriveFromPath)?;

Keypair::from_secret_key(private.to_bytes())
}

/// Turn a 16, 32 or 64 byte seed into a keypair.
///
/// # Warning
///
/// This will only be secure if the seed is secure!
pub fn from_seed(seed: &[u8]) -> Result<Self, Error> {
let private = bip32::XPrv::new(seed).map_err(|_| Error::InvalidSeed)?;
Keypair::from_secret_key(private.to_bytes())
}

/// Turn a 32 byte secret key into a keypair.
///
/// # Warning
///
/// This will only be secure if the secret key is secure!
pub fn from_secret_key(secret_key: SecretKeyBytes) -> Result<Self, Error> {
ecdsa::Keypair::from_secret_key(secret_key)
.map(Self)
.map_err(|_| Error::InvalidSeed)
}

/// Obtain the [`ecdsa::PublicKey`] of this keypair.
pub fn public_key(&self) -> ecdsa::PublicKey {
self.0.public_key()
}

/// Obtains the public address of the account by taking the last 20 bytes
/// of the Keccak-256 hash of the public key.
pub fn account_id(&self) -> AccountId20 {
let uncompressed = self.0 .0.public_key().serialize_uncompressed();
let hash = keccak(&uncompressed[1..]).0;
let hash20 = hash[12..].try_into().expect("should be 20 bytes");
AccountId20(hash20)
}

/// Signs an arbitrary message payload.
pub fn sign(&self, signer_payload: &[u8]) -> Signature {
self.sign_prehashed(&keccak(signer_payload).0)
}

/// Signs a pre-hashed message.
pub fn sign_prehashed(&self, message_hash: &[u8; 32]) -> Signature {
Signature(self.0.sign_prehashed(message_hash).0)
}
}

impl<T: Config> SignerT<T> for Keypair
where
T::AccountId: From<AccountId20>,
T::Address: From<AccountId20>,
T::AccountId: From<PublicKey>,
T::Address: From<PublicKey>,
T::Signature: From<Signature>,
{
fn account_id(&self) -> T::AccountId {
self.account_id().into()
self.public_key().into()
}

fn address(&self) -> T::Address {
self.account_id().into()
self.public_key().into()
}

fn sign(&self, signer_payload: &[u8]) -> T::Signature {
self.sign(signer_payload).into()
}
}

impl From<Keypair> for AccountId20 {
fn from(value: Keypair) -> Self {
value.account_id()
}
}

impl From<ecdsa::PublicKey> for AccountId20 {
fn from(value: ecdsa::PublicKey) -> Self {
let pk = secp256k1::PublicKey::from_slice(value.as_ref()).expect("Invalid public key");
let uncompressed = pk.serialize_uncompressed();
let hash = keccak(&uncompressed[1..]).0;
impl PublicKey {
/// Obtains the public address of the account by taking the last 20 bytes
/// of the Keccak-256 hash of the public key.
pub fn to_account_id(&self) -> AccountId20 {
let hash = keccak(&self.0[1..]).0;
let hash20 = hash[12..].try_into().expect("should be 20 bytes");
AccountId20(hash20)
}
/// A shortcut to obtain a [`MultiAddress`] from a [`PublicKey`].
/// We often want this type, and using this method avoids any
/// ambiguous type resolution issues.
pub fn to_address<T>(self) -> MultiAddress<AccountId20, T> {
MultiAddress::Address20(self.to_account_id().0)
}
}

impl<T> From<ecdsa::PublicKey> for MultiAddress<AccountId20, T> {
fn from(value: ecdsa::PublicKey) -> Self {
let address: AccountId20 = value.into();
MultiAddress::Address20(address.0)
impl From<PublicKey> for AccountId20 {
fn from(value: PublicKey) -> Self {
value.to_account_id()
}
}

impl<T> From<Keypair> for MultiAddress<AccountId20, T> {
fn from(value: Keypair) -> Self {
impl<T> From<PublicKey> for MultiAddress<AccountId20, T> {
fn from(value: PublicKey) -> Self {
let address: AccountId20 = value.into();
MultiAddress::Address20(address.0)
}
Expand Down Expand Up @@ -373,7 +374,7 @@ mod test {
fn check_subxt_signer_implementation_matches(keypair in keypair(), msg in ".*") {
let msg_as_bytes = msg.as_bytes();

assert_eq!(SubxtSigner::account_id(&keypair), keypair.account_id());
assert_eq!(SubxtSigner::account_id(&keypair), keypair.public_key().to_account_id());
assert_eq!(SubxtSigner::sign(&keypair, msg_as_bytes), keypair.sign(msg_as_bytes));
}

Expand All @@ -386,9 +387,9 @@ mod test {
let hash20 = hash[12..].try_into().expect("should be 20 bytes");
AccountId20(hash20)
};
let account_id_derived_from_pk: AccountId20 = keypair.public_key().into();
let account_id_derived_from_pk: AccountId20 = keypair.public_key().to_account_id();
assert_eq!(account_id_derived_from_pk, account_id);
assert_eq!(keypair.account_id(), account_id);
assert_eq!(keypair.public_key().to_account_id(), account_id);

}

Expand Down Expand Up @@ -447,7 +448,7 @@ mod test {
];

for (case_idx, (keypair, exp_account_id, exp_priv_key)) in cases.into_iter().enumerate() {
let act_account_id = keypair.account_id().checksum();
let act_account_id = keypair.public_key().to_account_id().checksum();
let act_priv_key = format!("0x{}", &keypair.0 .0.display_secret());

assert_eq!(
Expand Down Expand Up @@ -592,7 +593,7 @@ mod test {
fn test_account_derivation_1() {
let kp = Keypair::from_secret_key(KEY_1).expect("valid keypair");
assert_eq!(
kp.account_id().checksum(),
kp.public_key().to_account_id().checksum(),
"0x976f8456E4e2034179B284A23C0e0c8f6d3da50c"
);
}
Expand All @@ -601,7 +602,7 @@ mod test {
fn test_account_derivation_2() {
let kp = Keypair::from_secret_key(KEY_2).expect("valid keypair");
assert_eq!(
kp.account_id().checksum(),
kp.public_key().to_account_id().checksum(),
"0x420e9F260B40aF7E49440ceAd3069f8e82A5230f"
);
}
Expand All @@ -610,7 +611,7 @@ mod test {
fn test_account_derivation_3() {
let kp = Keypair::from_secret_key(KEY_3).expect("valid keypair");
assert_eq!(
kp.account_id().checksum(),
kp.public_key().to_account_id().checksum(),
"0x9cce34F7aB185c7ABA1b7C8140d620B4BDA941d6"
);
}
Expand Down

0 comments on commit bc24b52

Please sign in to comment.