use core::marker::PhantomData;
use blake2b_simd::Params as Blake2bParams;
use subtle::{Choice, ConstantTimeEq};
use zcash_spec::{PrfExpand, VariableLengthSlice};
use crate::{ChainCode, ChildIndex};
pub(crate) type HardenedOnlyCkdDomain =
PrfExpand<([u8; 32], [u8; 4], [u8; 1], VariableLengthSlice)>;
pub trait Context {
const MKG_DOMAIN: [u8; 16];
const CKD_DOMAIN: HardenedOnlyCkdDomain;
}
#[derive(Clone, Debug)]
pub struct HardenedOnlyKey<C: Context> {
sk: [u8; 32],
chain_code: ChainCode,
_context: PhantomData<C>,
}
impl<C: Context> ConstantTimeEq for HardenedOnlyKey<C> {
fn ct_eq(&self, rhs: &Self) -> Choice {
self.chain_code.ct_eq(&rhs.chain_code) & self.sk.ct_eq(&rhs.sk)
}
}
#[allow(non_snake_case)]
impl<C: Context> HardenedOnlyKey<C> {
pub(crate) fn from_parts(sk: [u8; 32], chain_code: ChainCode) -> Self {
Self {
sk,
chain_code,
_context: PhantomData,
}
}
pub fn parts(&self) -> (&[u8; 32], &ChainCode) {
(&self.sk, &self.chain_code)
}
pub(crate) fn into_parts(self) -> ([u8; 32], ChainCode) {
(self.sk, self.chain_code)
}
pub fn master(ikm: &[&[u8]]) -> Self {
let I: [u8; 64] = {
let mut I = Blake2bParams::new()
.hash_length(64)
.personal(&C::MKG_DOMAIN)
.to_state();
for input in ikm {
I.update(input);
}
I.finalize().as_bytes().try_into().expect("64-byte output")
};
Self::from_bytes(&I)
}
pub fn derive_child(&self, index: ChildIndex) -> Self {
self.derive_child_with_tag(index, &[])
}
pub fn derive_child_with_tag(&self, index: ChildIndex, tag: &[u8]) -> Self {
Self::from_bytes(&self.ckdh_internal(index, 0, tag))
}
pub(crate) fn ckdh_internal(&self, index: ChildIndex, lead: u8, tag: &[u8]) -> [u8; 64] {
C::CKD_DOMAIN.with(
self.chain_code.as_bytes(),
&self.sk,
&index.index().to_le_bytes(),
&[lead],
tag,
)
}
fn from_bytes(I: &[u8; 64]) -> Self {
let (I_L, I_R) = I.split_at(32);
let sk = I_L.try_into().unwrap();
let chain_code = ChainCode::new(I_R.try_into().unwrap());
Self {
sk,
chain_code,
_context: PhantomData,
}
}
}