use generic_ec::{Curve, Point, Scalar};
use hmac::Mac as _;
use crate::{
DeriveShift, DerivedShift, ExtendedKeyPair, ExtendedPublicKey, HardenedIndex, NonHardenedIndex,
};
type HmacSha512 = hmac::Hmac<sha2::Sha512>;
pub struct Slip10;
impl Slip10 {
fn derive_public_shift_for_any_curve<E: Curve>(
parent_public_key: &ExtendedPublicKey<E>,
child_index: NonHardenedIndex,
) -> DerivedShift<E> {
let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code)
.expect("this never fails: hmac can handle keys of any size");
let i = hmac
.clone()
.chain_update(parent_public_key.public_key.to_bytes(true))
.chain_update(child_index.to_be_bytes())
.finalize()
.into_bytes();
Self::calculate_shift_for_any_curve(&hmac, parent_public_key, *child_index, i)
}
fn derive_hardened_shift_for_any_curve<E: Curve>(
parent_key: &ExtendedKeyPair<E>,
child_index: HardenedIndex,
) -> DerivedShift<E> {
let hmac = HmacSha512::new_from_slice(parent_key.chain_code())
.expect("this never fails: hmac can handle keys of any size");
let i = hmac
.clone()
.chain_update([0x00])
.chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes())
.chain_update(child_index.to_be_bytes())
.finalize()
.into_bytes();
Self::calculate_shift_for_any_curve(&hmac, &parent_key.public_key, *child_index, i)
}
fn calculate_shift_for_any_curve<E: Curve>(
hmac: &HmacSha512,
parent_public_key: &ExtendedPublicKey<E>,
child_index: u32,
mut i: hmac::digest::Output<HmacSha512>,
) -> DerivedShift<E> {
loop {
let (i_left, i_right) = split_into_two_halves(&i);
if let Ok(shift) = Scalar::<E>::from_be_bytes(i_left) {
let child_pk = parent_public_key.public_key + Point::generator() * shift;
if !child_pk.is_zero() {
return DerivedShift {
shift,
child_public_key: ExtendedPublicKey {
public_key: child_pk,
chain_code: (*i_right).into(),
},
};
}
}
i = hmac
.clone()
.chain_update([0x01])
.chain_update(i_right)
.chain_update(child_index.to_be_bytes())
.finalize()
.into_bytes()
}
}
}
fn split_into_two_halves(
i: &generic_array::GenericArray<u8, generic_array::typenum::U64>,
) -> (
&generic_array::GenericArray<u8, generic_array::typenum::U32>,
&generic_array::GenericArray<u8, generic_array::typenum::U32>,
) {
generic_array::sequence::Split::split(i)
}
impl<E> DeriveShift<E> for Slip10
where
E: Curve + SupportedCurve,
{
fn derive_public_shift(
parent_public_key: &ExtendedPublicKey<E>,
child_index: NonHardenedIndex,
) -> DerivedShift<E> {
Slip10::derive_public_shift_for_any_curve(parent_public_key, child_index)
}
fn derive_hardened_shift(
parent_key: &ExtendedKeyPair<E>,
child_index: HardenedIndex,
) -> DerivedShift<E> {
Slip10::derive_hardened_shift_for_any_curve(parent_key, child_index)
}
}
pub trait SupportedCurve {
const CURVE_TYPE: CurveType;
}
#[cfg(feature = "curve-secp256k1")]
impl SupportedCurve for generic_ec::curves::Secp256k1 {
const CURVE_TYPE: CurveType = CurveType::Secp256k1;
}
#[cfg(feature = "curve-secp256r1")]
impl SupportedCurve for generic_ec::curves::Secp256r1 {
const CURVE_TYPE: CurveType = CurveType::Secp256r1;
}
#[derive(Clone, Copy, Debug)]
pub enum CurveType {
Secp256k1,
Secp256r1,
}
pub fn derive_master_key<E: generic_ec::Curve + SupportedCurve>(
seed: &[u8],
) -> Result<crate::ExtendedSecretKey<E>, crate::errors::InvalidLength> {
let curve_tag = match E::CURVE_TYPE {
CurveType::Secp256k1 => "Bitcoin seed",
CurveType::Secp256r1 => "Nist256p1 seed",
};
derive_master_key_with_curve_tag(curve_tag.as_bytes(), seed)
}
pub fn derive_master_key_with_curve_tag<E: generic_ec::Curve>(
curve_tag: &[u8],
seed: &[u8],
) -> Result<crate::ExtendedSecretKey<E>, crate::errors::InvalidLength> {
if !(16 <= seed.len() && seed.len() <= 64) {
return Err(crate::errors::InvalidLength);
}
let hmac = HmacSha512::new_from_slice(curve_tag)
.expect("this never fails: hmac can handle keys of any size");
let mut i = hmac.clone().chain_update(seed).finalize().into_bytes();
loop {
let (i_left, i_right) = split_into_two_halves(&i);
if let Ok(mut sk) = generic_ec::Scalar::<E>::from_be_bytes(i_left) {
if !bool::from(subtle::ConstantTimeEq::ct_eq(
&sk,
&generic_ec::Scalar::zero(),
)) {
return Ok(crate::ExtendedSecretKey {
secret_key: generic_ec::SecretScalar::new(&mut sk),
chain_code: (*i_right).into(),
});
}
}
i = hmac.clone().chain_update(&i[..]).finalize().into_bytes()
}
}
super::create_aliases!(Slip10, slip10);