keyfork-derive-util: add arbitrary length seeds, remove length-based errors

This commit is contained in:
Ryan Heywood 2024-02-11 20:35:26 -05:00
parent 8108f5e61a
commit 4354be4304
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
5 changed files with 29 additions and 50 deletions

View File

@ -10,18 +10,10 @@ const KEY_SIZE: usize = 256;
/// Errors associated with creating or deriving Extended Private Keys. /// Errors associated with creating or deriving Extended Private Keys.
#[derive(Error, Clone, Debug)] #[derive(Error, Clone, Debug)]
pub enum Error { pub enum Error {
/// The seed has an unsuitable length; supported lengths are 16 bytes, 32 bytes, or 64 bytes.
#[error("Seed had an unsuitable length: {0}")]
BadSeedLength(usize),
/// The maximum depth for key derivation has been reached. The supported maximum depth is 255. /// The maximum depth for key derivation has been reached. The supported maximum depth is 255.
#[error("Reached maximum depth for key derivation")] #[error("Reached maximum depth for key derivation")]
Depth, Depth,
/// This should never happen. HMAC keys should be able to take any size input.
#[error("Invalid length for HMAC key while generating master key (report me!)")]
HmacInvalidLength(#[from] hmac::digest::InvalidLength),
/// An unknown error occurred while deriving a child key. /// An unknown error occurred while deriving a child key.
#[error("Unknown error while deriving child key")] #[error("Unknown error while deriving child key")]
Derivation, Derivation,
@ -98,7 +90,7 @@ where
/// # Errors /// # Errors
/// An error may be returned if: /// An error may be returned if:
/// * The given seed had an incorrect length. /// * The given seed had an incorrect length.
/// * A `HmacSha512` can't be constructed - this should be impossible. /// * A `HmacSha512` can't be constructed.
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
@ -107,37 +99,30 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// # Ok(())
/// # }
/// ``` /// ```
pub fn new(seed: impl AsRef<[u8]>) -> Result<Self> { pub fn new(seed: impl AsRef<[u8]>) -> Self {
Self::new_internal(seed.as_ref()) Self::new_internal(seed.as_ref())
} }
fn new_internal(seed: &[u8]) -> Result<Self> { fn new_internal(seed: &[u8]) -> Self {
let len = seed.len(); let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>())
if ![16, 32, 64].contains(&len) { .expect("HmacSha512 InvalidLength should be infallible")
return Err(Error::BadSeedLength(len));
}
let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::<Vec<_>>())?
.chain_update(seed) .chain_update(seed)
.finalize() .finalize()
.into_bytes(); .into_bytes();
let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8); let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8);
Ok(Self::new_from_parts( Self::new_from_parts(
private_key private_key
.try_into() .try_into()
.expect("KEY_SIZE / 8 did not give a 32 byte slice"), .expect("KEY_SIZE / 8 did not give a 32 byte slice"),
0, 0,
// Checked: chain_code is always the same length, hash is static size // Checked: chain_code is always the same length, hash is static size
chain_code.try_into().expect("Invalid chain code length"), chain_code.try_into().expect("Invalid chain code length"),
)) )
} }
/// Create an [`ExtendedPrivateKey`] from a given `seed`, `depth`, and `chain_code`. /// Create an [`ExtendedPrivateKey`] from a given `seed`, `depth`, and `chain_code`.
@ -160,7 +145,7 @@ where
/// ``` /// ```
pub fn new_from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Self { pub fn new_from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Self {
Self { Self {
private_key: K::from_bytes(&key), private_key: K::from_bytes(key),
depth, depth,
chain_code, chain_code,
} }
@ -206,7 +191,7 @@ where
/// # 102, 201, 210, 159, 219, 222, 42, 201, 44, 196, 27, /// # 102, 201, 210, 159, 219, 222, 42, 201, 44, 196, 27,
/// # 90, 221, 80, 85, 135, 79, 39, 253, 223, 35, 251 /// # 90, 221, 80, 85, 135, 79, 39, 253, 223, 35, 251
/// # ]; /// # ];
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let xpub = xprv.extended_public_key(); /// let xpub = xprv.extended_public_key();
/// assert_eq!(known_key, xpub.public_key().to_bytes()); /// assert_eq!(known_key, xpub.public_key().to_bytes());
/// # Ok(()) /// # Ok(())
@ -230,7 +215,7 @@ where
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let pubkey = xprv.public_key(); /// let pubkey = xprv.public_key();
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -248,15 +233,12 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = // /// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
/// assert_eq!(xprv.depth(), 4); /// assert_eq!(xprv.depth(), 4);
/// # Ok(())
/// # }
/// ``` /// ```
pub fn depth(&self) -> u8 { pub fn depth(&self) -> u8 {
self.depth self.depth
@ -271,15 +253,12 @@ where
/// # public_key::TestPublicKey as PublicKey, /// # public_key::TestPublicKey as PublicKey,
/// # private_key::TestPrivateKey as PrivateKey, /// # private_key::TestPrivateKey as PrivateKey,
/// # }; /// # };
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let key: &[u8; 32] = // /// let key: &[u8; 32] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code)?; /// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code);
/// assert_eq!(chain_code, &xprv.chain_code()); /// assert_eq!(chain_code, &xprv.chain_code());
/// # Ok(())
/// # }
/// ``` /// ```
pub fn chain_code(&self) -> [u8; 32] { pub fn chain_code(&self) -> [u8; 32] {
self.chain_code self.chain_code
@ -301,7 +280,7 @@ where
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let path = DerivationPath::default() /// let path = DerivationPath::default()
/// .chain_push(DerivationIndex::new(44, true)?) /// .chain_push(DerivationIndex::new(44, true)?)
/// .chain_push(DerivationIndex::new(0, true)?) /// .chain_push(DerivationIndex::new(0, true)?)
@ -347,7 +326,7 @@ where
/// # fn main() -> Result<(), Box<dyn std::error::Error>> { /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let seed: &[u8; 64] = // /// let seed: &[u8; 64] = //
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed)?; /// let root_xprv = ExtendedPrivateKey::<PrivateKey>::new(seed);
/// let bip44_wallet = DerivationPath::default() /// let bip44_wallet = DerivationPath::default()
/// .chain_push(DerivationIndex::new(44, true)?) /// .chain_push(DerivationIndex::new(44, true)?)
/// .chain_push(DerivationIndex::new(0, true)?) /// .chain_push(DerivationIndex::new(0, true)?)
@ -363,8 +342,8 @@ where
pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> { pub fn derive_child(&self, index: &DerivationIndex) -> Result<Self> {
let depth = self.depth.checked_add(1).ok_or(Error::Depth)?; let depth = self.depth.checked_add(1).ok_or(Error::Depth)?;
let mut hmac = let mut hmac = HmacSha512::new_from_slice(&self.chain_code)
HmacSha512::new_from_slice(&self.chain_code).map_err(Error::HmacInvalidLength)?; .expect("HmacSha512 InvalidLength should be infallible");
if index.is_hardened() { if index.is_hardened() {
hmac.update(&[0]); hmac.update(&[0]);
hmac.update(&self.private_key.to_bytes()); hmac.update(&self.private_key.to_bytes());

View File

@ -68,7 +68,7 @@ impl DerivationAlgorithm {
match self { match self {
#[cfg(feature = "ed25519")] #[cfg(feature = "ed25519")]
Self::Ed25519 => { Self::Ed25519 => {
let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed)?; let key = ExtendedPrivateKey::<ed25519_dalek::SigningKey>::new(seed);
let derived_key = key.derive_path(path)?; let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv( Ok(DerivationResponse::with_algo_and_xprv(
self.clone(), self.clone(),
@ -77,7 +77,7 @@ impl DerivationAlgorithm {
} }
#[cfg(feature = "secp256k1")] #[cfg(feature = "secp256k1")]
Self::Secp256k1 => { Self::Secp256k1 => {
let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed)?; let key = ExtendedPrivateKey::<k256::SecretKey>::new(seed);
let derived_key = key.derive_path(path)?; let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv( Ok(DerivationResponse::with_algo_and_xprv(
self.clone(), self.clone(),
@ -85,7 +85,7 @@ impl DerivationAlgorithm {
)) ))
} }
Self::Internal => { Self::Internal => {
let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed)?; let key = ExtendedPrivateKey::<TestPrivateKey>::new(seed);
let derived_key = key.derive_path(path)?; let derived_key = key.derive_path(path)?;
Ok(DerivationResponse::with_algo_and_xprv( Ok(DerivationResponse::with_algo_and_xprv(
self.clone(), self.clone(),

View File

@ -30,7 +30,7 @@ fn secp256k1() {
} = test; } = test;
// Tests for ExtendedPrivateKey // Tests for ExtendedPrivateKey
let xkey = ExtendedPrivateKey::<SecretKey>::new(seed).unwrap(); let xkey = ExtendedPrivateKey::<SecretKey>::new(seed);
let derived_key = xkey.derive_path(&chain).unwrap(); let derived_key = xkey.derive_path(&chain).unwrap();
assert_eq!( assert_eq!(
derived_key.chain_code().as_slice(), derived_key.chain_code().as_slice(),
@ -51,7 +51,7 @@ fn secp256k1() {
// Tests for DerivationRequest // Tests for DerivationRequest
let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain); let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain);
let response = request.derive_with_master_seed(seed.clone()).unwrap(); let response = request.derive_with_master_seed(seed.clone()).unwrap();
assert_eq!(&response.data, private_key, "test: {chain}"); assert_eq!(&response.data, private_key.as_slice(), "test: {chain}");
} }
} }
} }
@ -75,7 +75,7 @@ fn ed25519() {
} = test; } = test;
// Tests for ExtendedPrivateKey // Tests for ExtendedPrivateKey
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap(); let xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
let derived_key = xkey.derive_path(&chain).unwrap(); let derived_key = xkey.derive_path(&chain).unwrap();
assert_eq!( assert_eq!(
derived_key.chain_code().as_slice(), derived_key.chain_code().as_slice(),
@ -96,7 +96,7 @@ fn ed25519() {
// Tests for DerivationRequest // Tests for DerivationRequest
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain); let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain);
let response = request.derive_with_master_seed(seed.to_vec()).unwrap(); let response = request.derive_with_master_seed(seed.to_vec()).unwrap();
assert_eq!(&response.data, private_key, "test: {chain}"); assert_eq!(&response.data, private_key.as_slice(), "test: {chain}");
} }
} }
} }
@ -108,7 +108,7 @@ fn panics_with_unhardened_derivation() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
let seed = hex!("000102030405060708090a0b0c0d0e0f"); let seed = hex!("000102030405060708090a0b0c0d0e0f");
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap(); let xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
xkey.derive_path(&DerivationPath::from_str("m/0").unwrap()) xkey.derive_path(&DerivationPath::from_str("m/0").unwrap())
.unwrap(); .unwrap();
} }
@ -120,7 +120,7 @@ fn panics_at_depth() {
use ed25519_dalek::SigningKey; use ed25519_dalek::SigningKey;
let seed = hex!("000102030405060708090a0b0c0d0e0f"); let seed = hex!("000102030405060708090a0b0c0d0e0f");
let mut xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap(); let mut xkey = ExtendedPrivateKey::<SigningKey>::new(seed);
for i in 0..=u32::from(u8::MAX) { for i in 0..=u32::from(u8::MAX) {
xkey = xkey xkey = xkey
.derive_child(&DerivationIndex::new(i, true).unwrap()) .derive_child(&DerivationIndex::new(i, true).unwrap())

View File

@ -648,7 +648,7 @@ pub fn combine(
// TODO: extract as function // TODO: extract as function
let userid = UserID::from("keyfork-sss"); let userid = UserID::from("keyfork-sss");
let path = DerivationPath::from_str("m/7366512'/0'")?; let path = DerivationPath::from_str("m/7366512'/0'")?;
let xprv = XPrv::new(&secret)?.derive_path(&path)?; let xprv = XPrv::new(&secret).derive_path(&path)?;
let derived_cert = keyfork_derive_openpgp::derive( let derived_cert = keyfork_derive_openpgp::derive(
xprv, xprv,
&[KeyFlags::empty().set_certification().set_signing()], &[KeyFlags::empty().set_certification().set_signing()],
@ -682,7 +682,7 @@ pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write)
// build cert to sign encrypted shares // build cert to sign encrypted shares
let userid = UserID::from("keyfork-sss"); let userid = UserID::from("keyfork-sss");
let path = DerivationPath::from_str("m/7366512'/0'")?; let path = DerivationPath::from_str("m/7366512'/0'")?;
let xprv = XPrv::new(&secret)?.derive_path(&path)?; let xprv = XPrv::new(&secret).derive_path(&path)?;
let derived_cert = keyfork_derive_openpgp::derive( let derived_cert = keyfork_derive_openpgp::derive(
xprv, xprv,
&[KeyFlags::empty().set_certification().set_signing()], &[KeyFlags::empty().set_certification().set_signing()],

View File

@ -42,7 +42,7 @@ fn derive_key(seed: &[u8], index: u8) -> Result<Cert> {
.chain_push(chain) .chain_push(chain)
.chain_push(account) .chain_push(account)
.chain_push(subkey); .chain_push(subkey);
let xprv = XPrv::new(seed)?.derive_path(&path)?; let xprv = XPrv::new(seed).derive_path(&path)?;
let userid = UserID::from(format!("Keyfork Shard {index}")); let userid = UserID::from(format!("Keyfork Shard {index}"));
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?; let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
Ok(cert) Ok(cert)