use crate::keys::{PublicKey, SecretKey, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH};
use crate::SchnorrError;
use bacteria::Transcript;
use mohan::dalek::{constants, ristretto::CompressedRistretto, scalar::Scalar};
use rand::{CryptoRng, Rng};
use std::fmt::Debug;
use subtle::{Choice, ConstantTimeEq};
use zeroize::Zeroize;
const EXTENDED_PUBLIC_KEY_NONCE_LENGTH: usize = 32;
pub const EXTENDED_PUBLIC_KEY_LENGTH: usize = PUBLIC_KEY_LENGTH + EXTENDED_PUBLIC_KEY_NONCE_LENGTH;
const EXTENDED_SECRET_KEY_NONCE_LENGTH: usize = 32;
pub const EXTENDED_SECRET_KEY_LENGTH: usize = SECRET_KEY_LENGTH + EXTENDED_SECRET_KEY_NONCE_LENGTH;
#[derive(Clone, Default)]
pub struct XSecretKey {
pub(crate) key: SecretKey,
pub(crate) xpub: XPublicKey,
}
impl ::zeroize::Zeroize for XSecretKey {
fn zeroize(&mut self) {
self.key.zeroize();
}
}
impl Drop for XSecretKey {
fn drop(&mut self) {
self.zeroize();
}
}
impl Debug for XSecretKey {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
write!(
f,
"XSecretKey {{ key: {:?} nonce: {:?} }}",
&self.key, &self.xpub.derivation_key
)
}
}
impl Eq for XSecretKey {}
impl PartialEq for XSecretKey {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).unwrap_u8() == 1u8
}
}
impl ConstantTimeEq for XSecretKey {
fn ct_eq(&self, other: &Self) -> Choice {
self.key.ct_eq(&other.key)
}
}
impl From<&SecretKey> for XSecretKey {
fn from(msk: &SecretKey) -> XSecretKey {
XSecretKey::from_secret(msk)
}
}
impl XSecretKey {
#[inline]
pub fn to_bytes(&self) -> [u8; EXTENDED_SECRET_KEY_LENGTH] {
let mut bytes: [u8; 64] = [0u8; 64];
let key = self.key.to_bytes();
bytes[..32].copy_from_slice(&key[..]);
bytes[32..].copy_from_slice(&self.xpub.derivation_key[..]);
bytes
}
#[inline]
pub fn from_bytes(bytes: &[u8]) -> Result<XSecretKey, SchnorrError> {
if bytes.len() != EXTENDED_SECRET_KEY_LENGTH {
return Err(SchnorrError::BadArguments);
}
let mut key: [u8; 32] = [0u8; 32];
key.copy_from_slice(&bytes[00..32]);
let scalar = Scalar::from_bits(key);
let mut nonce: [u8; 32] = [0u8; 32];
nonce.copy_from_slice(&bytes[32..64]);
let key_t = SecretKey(scalar);
let pk_t = PublicKey::from_secret(&key_t);
Ok(XSecretKey {
key: key_t,
xpub: XPublicKey {
key: pk_t,
derivation_key: nonce,
},
})
}
pub fn generate<R>(mut csprng: R) -> XSecretKey
where
R: CryptoRng + Rng,
{
let scalar = Scalar::random(&mut csprng);
let mut nonce: [u8; 32] = [0u8; 32];
csprng.fill_bytes(&mut nonce);
let key_t = SecretKey(scalar);
let pk_t = PublicKey::from_secret(&key_t);
XSecretKey {
key: key_t,
xpub: XPublicKey {
key: pk_t,
derivation_key: nonce,
},
}
}
pub fn to_public(&self) -> XPublicKey {
self.xpub.clone()
}
pub fn as_public(&self) -> &XPublicKey {
&self.xpub
}
pub fn from_secret(secret: &SecretKey) -> XSecretKey {
let mut t = Transcript::new(b"Keytree.derivation");
t.append_message(b"secret_key", secret.as_bytes());
let key = t.challenge_scalar(b"f.leaf");
let mut nonce = [0u8; 32];
t.challenge_bytes(b"f.intermediate", &mut nonce);
XSecretKey {
key: SecretKey(key),
xpub: XPublicKey {
key: PublicKey::from_point(&key * &constants::RISTRETTO_BASEPOINT_POINT),
derivation_key: nonce,
},
}
}
pub fn derive_intermediate_key(&self, customize: impl FnOnce(&mut Transcript)) -> XSecretKey {
let (child_xpub, f) = self
.xpub
.derive_intermediate_helper(self.xpub.prepare_prf(), customize);
XSecretKey {
key: SecretKey::from_scalar(self.key.as_scalar() + f),
xpub: child_xpub,
}
}
pub fn derive_key(&self, customize: impl FnOnce(&mut Transcript)) -> SecretKey {
let f = self
.xpub
.derive_leaf_helper(self.xpub.prepare_prf(), customize);
SecretKey::from_scalar(self.key.as_scalar() + f)
}
}
#[derive(Default, Clone)]
pub struct XPublicKey {
pub key: PublicKey,
pub(crate) derivation_key: [u8; 32],
}
impl XPublicKey {
pub fn derive_intermediate_key(&self, customize: impl FnOnce(&mut Transcript)) -> XPublicKey {
let (xpub, _f) = self.derive_intermediate_helper(self.prepare_prf(), customize);
xpub
}
pub fn derive_key(&self, customize: impl FnOnce(&mut Transcript)) -> PublicKey {
let f = self.derive_leaf_helper(self.prepare_prf(), customize);
PublicKey::from_point(self.key.as_point() + (&f * &constants::RISTRETTO_BASEPOINT_POINT))
}
pub fn to_bytes(&self) -> [u8; EXTENDED_PUBLIC_KEY_LENGTH] {
let mut buf = [0u8; EXTENDED_PUBLIC_KEY_LENGTH];
buf[..32].copy_from_slice(self.key.as_bytes());
buf[32..].copy_from_slice(&self.derivation_key);
buf
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != EXTENDED_PUBLIC_KEY_LENGTH {
return None;
}
let precompressed_pubkey = CompressedRistretto::from_slice(&bytes[..32]);
let mut dk = [0u8; 32];
dk.copy_from_slice(&bytes[32..]);
let key = match PublicKey::from_compressed(precompressed_pubkey) {
Ok(p) => p,
Err(_) => return None,
};
Some(XPublicKey {
key: key,
derivation_key: dk,
})
}
fn prepare_prf(&self) -> Transcript {
let mut t = Transcript::new(b"Keytree.derivation");
t.commit_point(b"pt", self.key.as_compressed());
t.append_message(b"dk", &self.derivation_key);
t
}
fn derive_intermediate_helper(
&self,
mut prf: Transcript,
customize: impl FnOnce(&mut Transcript),
) -> (XPublicKey, Scalar) {
customize(&mut prf);
let f = prf.challenge_scalar(b"f.intermediate");
let mut child_dk = [0u8; 32];
prf.challenge_bytes(b"dk", &mut child_dk);
let child_point = self.key.as_point() + (&f * &constants::RISTRETTO_BASEPOINT_POINT);
let xpub = XPublicKey {
key: PublicKey::from_point(child_point),
derivation_key: child_dk,
};
(xpub, f)
}
fn derive_leaf_helper(
&self,
mut prf: Transcript,
customize: impl FnOnce(&mut Transcript),
) -> Scalar {
customize(&mut prf);
prf.challenge_scalar(b"f.leaf")
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex;
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
#[test]
fn test_vectors() {
let root_prv = XSecretKey::default();
let root_pub = root_prv.to_public();
assert_eq!(
to_hex_64(root_prv.to_bytes()),
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
assert_eq!(
to_hex_64(root_pub.to_bytes()),
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
let child_prv = root_prv.derive_intermediate_key(|prf| prf.append_u64(b"index", 1));
let child_pub = root_pub.derive_intermediate_key(|prf| prf.append_u64(b"index", 1));
let child2_prv = child_prv.derive_intermediate_key(|prf| prf.append_u64(b"index", 1));
let child2_pub = child_pub.derive_intermediate_key(|prf| prf.append_u64(b"index", 1));
let leaf_prv = child_prv.derive_key(|prf| prf.append_u64(b"index", 1));
let leaf_pub = child_pub.derive_key(|prf| prf.append_u64(b"index", 1));
}
#[test]
fn test_defaults() {
let default_xprv = XSecretKey::default();
let default_xpub = XPublicKey::default();
assert_eq!(
to_hex_64(default_xprv.to_bytes()),
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
assert_eq!(
to_hex_64(default_xpub.to_bytes()),
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
assert_eq!(
to_hex_64(default_xpub.to_bytes()),
to_hex_64(default_xprv.to_public().to_bytes())
);
let default_xprv = XSecretKey::from_bytes(&hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()).unwrap();
assert_eq!(
to_hex_64(default_xprv.to_bytes()),
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
let default_xpub = XPublicKey::from_bytes(&hex::decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()).unwrap();
assert_eq!(
to_hex_64(default_xpub.to_bytes()),
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
);
}
#[test]
fn random_xprv_test() {
let seed = [0u8; 32];
let xprv = XSecretKey::generate(&mut mohan::mohan_rand());
}
#[test]
fn random_xprv_derivation_test() {
let seed = [0u8; 32];
let mut rng = rand_chacha::ChaChaRng::from_seed(seed);
let xprv = XSecretKey::generate(&mut rng).derive_intermediate_key(|t| {
t.append_u64(b"account_id", 34);
});
}
#[test]
fn random_xprv_leaf_test() {
let seed = [0u8; 32];
let mut rng = rand_chacha::ChaChaRng::from_seed(seed);
let xprv = XSecretKey::generate(&mut rng).derive_key(|t| {
t.append_u64(b"invoice_id", 10034);
});
}
#[test]
fn serialize_xprv_test() {
let seed = [0u8; 32];
let mut rng = rand_chacha::ChaChaRng::from_seed(seed);
let xprv = XSecretKey::generate(&mut rng);
let xprv_bytes = xprv.to_bytes();
assert_eq!(
to_hex_64(xprv_bytes),
"4a53c3fbbc59970ee5f85af813875dffc13a904a2e53ae7e65fa0dea6e62c9019f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed"
);
}
#[test]
fn deserialize_xprv_test() {
let xprv_bytes = hex::decode("4a53c3fbbc59970ee5f85af813875dffc13a904a2e53ae7e65fa0dea6e62c9019f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed").unwrap();
let xprv = XSecretKey::from_bytes(&xprv_bytes).unwrap();
let seed = [0u8; 32];
let mut rng = rand_chacha::ChaChaRng::from_seed(seed);
let expected_xprv = XSecretKey::generate(&mut rng);
assert_eq!(xprv.xpub.derivation_key, expected_xprv.xpub.derivation_key);
assert_eq!(xprv.key, expected_xprv.key);
}
#[test]
fn random_xpub_test() {
let seed = [0u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let xprv = XSecretKey::generate(&mut rng);
let xpub = xprv.to_public();
assert_eq!(
to_hex_32(xpub.derivation_key),
"9f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed"
);
assert_eq!(
to_hex_32(xpub.key.to_bytes()),
"9c66a339c8344f922fc3206cb5dae814a594c0177dd3235c254d9c409a65b808"
);
}
#[test]
fn serialize_xpub_test() {
let seed = [0u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let xprv = XSecretKey::generate(&mut rng);
let xpub = xprv.to_public();
assert_eq!(
to_hex_64(xpub.to_bytes()),
"9c66a339c8344f922fc3206cb5dae814a594c0177dd3235c254d9c409a65b8089f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed"
);
}
#[test]
fn deserialize_xpub_test() {
let xpub_bytes = hex::decode("9c66a339c8344f922fc3206cb5dae814a594c0177dd3235c254d9c409a65b8089f07e7be5551387a98ba977c732d080dcb0f29a048e3656912c6533e32ee7aed").unwrap();
let xpub = XPublicKey::from_bytes(&xpub_bytes).unwrap();
let seed = [0u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let expected_xprv = XSecretKey::generate(&mut rng);
let expected_xpub = expected_xprv.to_public();
assert_eq!(xpub.derivation_key, expected_xpub.derivation_key);
assert_eq!(xpub.key, expected_xpub.key);
assert_eq!(
xpub.key.as_compressed(),
expected_xpub.key.as_compressed()
);
}
#[test]
fn random_xpub_derivation_test() {
let seed = [0u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let xprv = XSecretKey::generate(&mut rng);
let xpub = xprv.to_public().derive_intermediate_key(|t| {
t.append_u64(b"account_id", 34);
});
}
#[test]
fn random_xpub_leaf_test() {
let seed = [0u8; 32];
let mut rng = ChaChaRng::from_seed(seed);
let xprv = XSecretKey::generate(&mut rng);
let xpub = xprv.to_public().derive_key(|t| {
t.append_u64(b"invoice_id", 10034);
});
}
fn to_hex_32(input: [u8; 32]) -> std::string::String {
hex::encode(&input[..])
}
fn to_hex_64(input: [u8; 64]) -> std::string::String {
hex::encode(&input[..])
}
}