use crate::{
ChainCode, ChildNumber, Error, HmacSha512, KeyFingerprint, PrivateKeyBytes, Result, KEY_SIZE,
};
use hmac::Mac;
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};
#[cfg(feature = "secp256k1")]
use {
crate::XPub,
k256::elliptic_curve::{group::prime::PrimeCurveAffine, sec1::ToEncodedPoint},
};
pub type PublicKeyBytes = [u8; KEY_SIZE + 1];
pub trait PublicKey: Sized {
fn from_bytes(bytes: PublicKeyBytes) -> Result<Self>;
fn to_bytes(&self) -> PublicKeyBytes;
fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self>;
fn fingerprint(&self) -> KeyFingerprint {
let digest = Ripemd160::digest(Sha256::digest(self.to_bytes()));
digest[..4].try_into().expect("digest truncated")
}
fn derive_tweak(
&self,
chain_code: &ChainCode,
child_number: ChildNumber,
) -> Result<(PrivateKeyBytes, ChainCode)> {
if child_number.is_hardened() {
return Err(Error::ChildNumber);
}
let mut hmac = HmacSha512::new_from_slice(chain_code).map_err(|_| Error::Crypto)?;
hmac.update(&self.to_bytes());
hmac.update(&child_number.to_bytes());
let result = hmac.finalize().into_bytes();
let (tweak_bytes, chain_code_bytes) = result.split_at(KEY_SIZE);
let tweak = tweak_bytes.try_into()?;
let chain_code = chain_code_bytes.try_into()?;
Ok((tweak, chain_code))
}
}
#[cfg(feature = "secp256k1")]
impl PublicKey for k256::PublicKey {
fn from_bytes(bytes: PublicKeyBytes) -> Result<Self> {
Ok(k256::PublicKey::from_sec1_bytes(&bytes)?)
}
fn to_bytes(&self) -> PublicKeyBytes {
self.to_encoded_point(true)
.as_bytes()
.try_into()
.expect("malformed public key")
}
fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self> {
let child_scalar =
Option::<k256::NonZeroScalar>::from(k256::NonZeroScalar::from_repr(other.into()))
.ok_or(Error::Crypto)?;
let child_point = self.to_projective() + (k256::AffinePoint::generator() * *child_scalar);
Self::from_affine(child_point.into()).map_err(|_| Error::Crypto)
}
}
#[cfg(feature = "secp256k1")]
impl PublicKey for k256::ecdsa::VerifyingKey {
fn from_bytes(bytes: PublicKeyBytes) -> Result<Self> {
Ok(k256::ecdsa::VerifyingKey::from_sec1_bytes(&bytes)?)
}
fn to_bytes(&self) -> PublicKeyBytes {
k256::CompressedPoint::from(self)
.as_slice()
.try_into()
.expect("malformed key")
}
fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self> {
k256::PublicKey::from(self)
.derive_child(other)
.map(Into::into)
}
}
#[cfg(feature = "secp256k1")]
impl From<XPub> for k256::ecdsa::VerifyingKey {
fn from(xpub: XPub) -> k256::ecdsa::VerifyingKey {
k256::ecdsa::VerifyingKey::from(&xpub)
}
}
#[cfg(feature = "secp256k1")]
impl From<&XPub> for k256::ecdsa::VerifyingKey {
fn from(xpub: &XPub) -> k256::ecdsa::VerifyingKey {
*xpub.public_key()
}
}
#[cfg(feature = "secp256k1-ffi")]
impl PublicKey for secp256k1_ffi::PublicKey {
fn from_bytes(bytes: PublicKeyBytes) -> Result<Self> {
Ok(secp256k1_ffi::PublicKey::from_slice(&bytes)?)
}
fn to_bytes(&self) -> PublicKeyBytes {
self.serialize()
}
fn derive_child(&self, bytes: PrivateKeyBytes) -> Result<Self> {
use secp256k1_ffi::{Secp256k1, VerifyOnly};
let engine = Secp256k1::<VerifyOnly>::verification_only();
let scalar = secp256k1_ffi::Scalar::from_be_bytes(bytes)?;
Ok(self.add_exp_tweak(&engine, &scalar)?)
}
}
#[cfg(all(test, feature = "bip39", feature = "secp256k1-ffi"))]
mod tests {
use hex_literal::hex;
const SEED: [u8; 64] = hex!(
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a2
9f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"
);
type XPrv = crate::ExtendedPrivateKey<secp256k1_ffi::SecretKey>;
#[test]
fn secp256k1_ffi_xprv_derivation() {
let path = "m/0/2147483647'/1/2147483646'/2";
let xprv = XPrv::derive_from_path(&SEED, &path.parse().unwrap()).unwrap();
assert_eq!(
xprv.public_key(),
"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt".parse().unwrap()
);
}
#[test]
fn secp256k1_ffi_xpub_derivation() {
let path = "m/0/2147483647'/1/2147483646'";
let xprv = XPrv::derive_from_path(&SEED, &path.parse().unwrap()).unwrap();
let xpub = xprv.public_key().derive_child(2.into()).unwrap();
assert_eq!(
xpub,
"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt".parse().unwrap()
);
}
}