LFDT-Lockness / hd-wallet

SLIP10 implementation in Rust
Apache License 2.0
3 stars 0 forks source link

Support for importing raw key bytes #13

Open BHawleyWall opened 1 week ago

BHawleyWall commented 1 week ago

Hello - your implementation of slip-0010 is one of the few "in the wild" for Rust. I recently had need for implementing HD-keys in Rust and after exploring the options available, I liked your crate the most out of the options available. I was able to put together a decent implementation that was clean and readable thanks to your abstractions and was successful in generating and extending keys.

Unfortunately, I've reached a point where I now want to support extending keys (deriving some new step or steps, not from a seed) generated from other systems. Your ExtendedSecretKey and ExtendedKeyPair types do not seem to expose any constructors from a slice of bytes for the secret material and chain code.

Was this intentional, or would it be possible to request a way to "import" an ExtendedSecretKey from just the raw bytes for the key + chain code?

BHawleyWall commented 2 days ago

For anyone else who stumbles upon this as part of a search, here's how I solved it for my own use-case. Note that I've cobbled this example together from various files, so it may or may not compile without tweaking imports, etc. Primarily it is meant to showcase the steps necessary.

use aes_gcm_siv::{
    aead::{Aead, KeyInit, OsRng},
    AeadCore,
    Aes256GcmSiv,
};
use anyhow::{anyhow, Result};
use generic_ec::Curve;
use hd_wallet::{
    curves::{Ed25519, Secp256k1, Secp256r1},
    ExtendedSecretKey,
};
use zeroize::{Zeroize, Zeroizing, ZeroizeOnDrop};

#[derive(Clone, PartialEq, Default, Zeroize, ZeroizeOnDrop)]
pub struct SecretMaterial(pub(crate) Vec<u8>);

// snip various impl's for the newtype

impl<T: Curve> TryFrom<&SecretMaterial> for ExtendedSecretKey<T> {
    type Error = anyhow::Error;

    fn try_from(value: &SecretMaterial) -> Result<Self> {
        let scalar = SecretScalar::from_be_bytes(value.as_bytes())?;

        Ok(Self {
            secret_key: scalar,
            chain_code: [0u8; 32],
        })
    }
}

pub fn generate_256_key() -> SecretMaterial {
    let material = Zeroizing::new(Aes256GcmSiv::generate_key(&mut OsRng));

    SecretMaterial(material.as_slice().to_vec())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // dummy setup for example
    let first_half = generate_256_key().into_vec().into_iter();
    let second_half = generate_256_key().into_vec().into_iter();
    let seed = Zeroizing::new(first_half.chain(second_half).collect::<Vec<u8>>());
    let parent = SecretMaterial::from(seed.to_vec());
    let chain_code = [1u8; 32];

    // ...because we need both the secret and the chain code, 
    // and the target type provides no help to convert from 
    // Big Endian bytes alone, we do this in two steps
    // to avoid the compilation blocker for implementing on
    // tuples for the conversion trait
    let mut parent_key = ExtendedSecretKey::<Secp256k1>::try_from(parent)?;
    parent_key.chain_code = chain_code;
}