Skip to content

Commit

Permalink
Merge pull request #38 from Foundation-Devices/jeandudey/sft-3839-imp…
Browse files Browse the repository at this point in the history
…lement-owned-version-of-hdkey-in-foundation-urtypes

SFT-3839: Implement owned version of HDKey
  • Loading branch information
jeandudey authored Jul 17, 2024
2 parents 781278f + 8fe5eac commit c7b4318
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 46 deletions.
2 changes: 1 addition & 1 deletion urtypes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

[package]
name = "foundation-urtypes"
version = "0.4.1"
version = "0.5.0"
edition = "2021"
homepage.workspace = true
description = """
Expand Down
4 changes: 2 additions & 2 deletions urtypes/fuzz/fuzz_targets/hdkey_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

#![no_main]

use foundation_urtypes::registry::HDKey;
use foundation_urtypes::registry::HDKeyRef;
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
minicbor::decode::<'_, HDKey>(data).ok();
minicbor::decode::<'_, HDKeyRef>(data).ok();
});
122 changes: 97 additions & 25 deletions urtypes/src/registry/hdkey.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

#[cfg(feature = "alloc")]
use alloc::string::String;
use core::num::NonZeroU32;

use minicbor::{
bytes::DecodeBytes, data::Tag, data::Type, decode::Error, encode::Write, Decode, Decoder,
Encode, Encoder,
};

use crate::registry::{CoinInfo, Keypath};
#[cfg(feature = "alloc")]
use crate::registry::Keypath;
use crate::registry::{CoinInfo, KeypathRef};

/// HD Key.
/// HD Key (non owned, zero copy).
#[doc(alias("hd-key"))]
#[derive(Debug, Clone, PartialEq)]
pub enum HDKey<'a> {
pub enum HDKeyRef<'a> {
/// Master key.
MasterKey(MasterKey),
/// Derived key.
DerivedKey(DerivedKey<'a>),
DerivedKey(DerivedKeyRef<'a>),
}

impl<'a> HDKey<'a> {
/// The CBOR tag used when [`HDKey`] is embedded in other CBOR types.
impl<'a> HDKeyRef<'a> {
/// The CBOR tag used when [`HDKeyRef`] is embedded in other CBOR types.
pub const TAG: Tag = Tag::new(303);
}

#[cfg(feature = "bitcoin")]
impl<'a> TryFrom<&'a bitcoin::bip32::Xpriv> for HDKey<'a> {
impl<'a> TryFrom<&'a bitcoin::bip32::Xpriv> for HDKeyRef<'a> {
type Error = InterpretExtendedKeyError;

fn try_from(xprv: &'a bitcoin::bip32::Xpriv) -> Result<Self, Self::Error> {
Expand All @@ -42,7 +46,7 @@ impl<'a> TryFrom<&'a bitcoin::bip32::Xpriv> for HDKey<'a> {
key_data[0] = 0;
key_data[1..].copy_from_slice(&xprv.private_key.secret_bytes());

Ok(Self::DerivedKey(DerivedKey {
Ok(Self::DerivedKey(DerivedKeyRef {
is_private: true,
key_data,
chain_code: Some(xprv.chain_code.to_bytes()),
Expand All @@ -67,13 +71,13 @@ impl<'a> TryFrom<&'a bitcoin::bip32::Xpriv> for HDKey<'a> {
}

#[cfg(feature = "bitcoin")]
impl<'a> TryFrom<&'a bitcoin::bip32::Xpub> for HDKey<'a> {
impl<'a> TryFrom<&'a bitcoin::bip32::Xpub> for HDKeyRef<'a> {
type Error = InterpretExtendedKeyError;

fn try_from(xpub: &'a bitcoin::bip32::Xpub) -> Result<Self, Self::Error> {
use crate::registry::CoinType;

Ok(Self::DerivedKey(DerivedKey {
Ok(Self::DerivedKey(DerivedKeyRef {
is_private: false,
key_data: xpub.public_key.serialize(),
chain_code: Some(xpub.chain_code.to_bytes()),
Expand All @@ -100,14 +104,14 @@ impl<'a> TryFrom<&'a bitcoin::bip32::Xpub> for HDKey<'a> {
#[derive(Debug)]
pub struct InterpretExtendedKeyError;

impl<'b, C> Decode<'b, C> for HDKey<'b> {
impl<'b, C> Decode<'b, C> for HDKeyRef<'b> {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
if MasterKey::decode(&mut d.probe(), ctx).is_ok() {
return Ok(HDKey::MasterKey(MasterKey::decode(d, ctx)?));
return Ok(HDKeyRef::MasterKey(MasterKey::decode(d, ctx)?));
}

if DerivedKey::decode(&mut d.probe(), ctx).is_ok() {
return Ok(HDKey::DerivedKey(DerivedKey::decode(d, ctx)?));
if DerivedKeyRef::decode(&mut d.probe(), ctx).is_ok() {
return Ok(HDKeyRef::DerivedKey(DerivedKeyRef::decode(d, ctx)?));
}

Err(Error::message(
Expand All @@ -116,19 +120,45 @@ impl<'b, C> Decode<'b, C> for HDKey<'b> {
}
}

impl<'a, C> Encode<C> for HDKey<'a> {
impl<'a, C> Encode<C> for HDKeyRef<'a> {
fn encode<W: Write>(
&self,
e: &mut Encoder<W>,
ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
match self {
HDKey::MasterKey(master_key) => master_key.encode(e, ctx),
HDKey::DerivedKey(derived_key) => derived_key.encode(e, ctx),
HDKeyRef::MasterKey(master_key) => master_key.encode(e, ctx),
HDKeyRef::DerivedKey(derived_key) => derived_key.encode(e, ctx),
}
}
}

/// HD Key.
#[doc(alias("hd-key"))]
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
pub enum HDKey {
MasterKey(MasterKey),
DerivedKey(DerivedKey),
}

#[cfg(feature = "alloc")]
impl<'a> From<HDKeyRef<'a>> for HDKey {
fn from(hdkey: HDKeyRef<'a>) -> Self {
match hdkey {
HDKeyRef::MasterKey(m) => Self::MasterKey(m),
HDKeyRef::DerivedKey(d) => Self::DerivedKey(DerivedKey::from(d)),
}
}
}

#[cfg(feature = "alloc")]
impl<'b, C> Decode<'b, C> for HDKey {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
HDKeyRef::decode(d, ctx).map(HDKey::from)
}
}

/// A master key.
#[doc(alias("master-key"))]
#[derive(Debug, Clone, Eq, PartialEq)]
Expand Down Expand Up @@ -206,10 +236,10 @@ impl<C> Encode<C> for MasterKey {
}
}

/// A derived key.
/// A derived key (non-owned, zero copy).
#[doc(alias("derived-key"))]
#[derive(Debug, Clone, PartialEq)]
pub struct DerivedKey<'a> {
pub struct DerivedKeyRef<'a> {
/// `true` if key is private, `false` if public.
pub is_private: bool,
/// Key data bytes.
Expand All @@ -219,9 +249,9 @@ pub struct DerivedKey<'a> {
/// How the key is to be used.
pub use_info: Option<CoinInfo>,
/// How the key was derived.
pub origin: Option<Keypath<'a>>,
pub origin: Option<KeypathRef<'a>>,
/// What children should/can be derived from this.
pub children: Option<Keypath<'a>>,
pub children: Option<KeypathRef<'a>>,
/// The fingerprint of this key's direct ancestor.
pub parent_fingerprint: Option<NonZeroU32>,
/// A short name for this key.
Expand All @@ -230,7 +260,7 @@ pub struct DerivedKey<'a> {
pub note: Option<&'a str>,
}

impl<'b, C> Decode<'b, C> for DerivedKey<'b> {
impl<'b, C> Decode<'b, C> for DerivedKeyRef<'b> {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
let mut is_private = false;
let mut key_data = None;
Expand Down Expand Up @@ -266,11 +296,11 @@ impl<'b, C> Decode<'b, C> for DerivedKey<'b> {
_ => return Err(Error::message("invalid tag for coininfo")),
},
6 => match d.tag()? {
TAGGED_KEYPATH => origin = Some(Keypath::decode(d, ctx)?),
TAGGED_KEYPATH => origin = Some(KeypathRef::decode(d, ctx)?),
_ => return Err(Error::message("invalid tag for keypath")),
},
7 => match d.tag()? {
TAGGED_KEYPATH => children = Some(Keypath::decode(d, ctx)?),
TAGGED_KEYPATH => children = Some(KeypathRef::decode(d, ctx)?),
_ => return Err(Error::message("invalid tag for keypath")),
},
8 => {
Expand Down Expand Up @@ -299,7 +329,7 @@ impl<'b, C> Decode<'b, C> for DerivedKey<'b> {
}
}

impl<'a, C> Encode<C> for DerivedKey<'a> {
impl<'a, C> Encode<C> for DerivedKeyRef<'a> {
fn encode<W: Write>(
&self,
e: &mut Encoder<W>,
Expand Down Expand Up @@ -357,3 +387,45 @@ impl<'a, C> Encode<C> for DerivedKey<'a> {
Ok(())
}
}

/// A derived key.
#[doc(alias("derived-key"))]
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
pub struct DerivedKey {
/// `true` if key is private, `false` if public.
pub is_private: bool,
/// Key data bytes.
pub key_data: [u8; 33],
/// Optional chain code.
pub chain_code: Option<[u8; 32]>,
/// How the key is to be used.
pub use_info: Option<CoinInfo>,
/// How the key was derived.
pub origin: Option<Keypath>,
/// What children should/can be derived from this.
pub children: Option<Keypath>,
/// The fingerprint of this key's direct ancestor.
pub parent_fingerprint: Option<NonZeroU32>,
/// A short name for this key.
pub name: Option<String>,
/// An arbitrary amount of text describing the key.
pub note: Option<String>,
}

#[cfg(feature = "alloc")]
impl<'a> From<DerivedKeyRef<'a>> for DerivedKey {
fn from(derived_key: DerivedKeyRef<'a>) -> Self {
Self {
is_private: derived_key.is_private,
key_data: derived_key.key_data,
chain_code: derived_key.chain_code,
use_info: derived_key.use_info,
origin: derived_key.origin.map(Keypath::from),
children: derived_key.children.map(Keypath::from),
parent_fingerprint: derived_key.parent_fingerprint,
name: derived_key.name.map(String::from),
note: derived_key.note.map(String::from),
}
}
}
39 changes: 33 additions & 6 deletions urtypes/src/registry/keypath.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <[email protected]>
// SPDX-License-Identifier: GPL-3.0-or-later

#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::{num::NonZeroU32, ops::Range};

use minicbor::{data::Type, decode::Error, encode::Write, Decode, Decoder, Encode, Encoder};

/// Metadata for the complete or partial derivation path of a key.
/// Metadata for the complete or partial derivation path of a key
/// (non-owned, zero copy).
#[doc(alias("crypto-keypath"))]
#[derive(Debug, Clone, PartialEq)]
pub struct Keypath<'a> {
pub struct KeypathRef<'a> {
/// Path component.
pub components: PathComponents<'a>,
/// Fingerprint from the ancestor key.
Expand All @@ -17,7 +20,7 @@ pub struct Keypath<'a> {
pub depth: Option<u8>,
}

impl<'a> Keypath<'a> {
impl<'a> KeypathRef<'a> {
/// Create a new key path for a master extended public key.
///
/// The `source_fingerprint` parameter is the fingerprint of the master key.
Expand All @@ -32,7 +35,7 @@ impl<'a> Keypath<'a> {
}
}

impl<'b, C> Decode<'b, C> for Keypath<'b> {
impl<'b, C> Decode<'b, C> for KeypathRef<'b> {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
let mut components = None;
let mut source_fingerprint = None;
Expand Down Expand Up @@ -71,7 +74,7 @@ impl<'b, C> Decode<'b, C> for Keypath<'b> {
}
}

impl<'a, C> Encode<C> for Keypath<'a> {
impl<'a, C> Encode<C> for KeypathRef<'a> {
fn encode<W: Write>(
&self,
e: &mut Encoder<W>,
Expand All @@ -97,7 +100,7 @@ impl<'a, C> Encode<C> for Keypath<'a> {
}

#[cfg(feature = "bitcoin")]
impl<'a> From<&'a bitcoin::bip32::DerivationPath> for Keypath<'a> {
impl<'a> From<&'a bitcoin::bip32::DerivationPath> for KeypathRef<'a> {
fn from(derivation_path: &'a bitcoin::bip32::DerivationPath) -> Self {
Self {
components: PathComponents {
Expand All @@ -109,6 +112,30 @@ impl<'a> From<&'a bitcoin::bip32::DerivationPath> for Keypath<'a> {
}
}

/// Metadata for the complete or partial derivation path of a key.
#[doc(alias("crypto-keypath"))]
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
pub struct Keypath {
/// Path component.
pub components: Vec<PathComponent>,
/// Fingerprint from the ancestor key.
pub source_fingerprint: Option<NonZeroU32>,
/// How many derivations this key is from the master (which is 0).
pub depth: Option<u8>,
}

#[cfg(feature = "alloc")]
impl<'a> From<KeypathRef<'a>> for Keypath {
fn from(keypath: KeypathRef<'a>) -> Self {
Self {
components: keypath.components.iter().collect(),
source_fingerprint: keypath.source_fingerprint,
depth: keypath.depth,
}
}
}

/// Collection of [`PathComponents`].
#[derive(Debug, Clone)]
pub struct PathComponents<'a> {
Expand Down
8 changes: 4 additions & 4 deletions urtypes/src/registry/output_descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use minicbor::{

use foundation_arena::{boxed::Box, Arena};

use crate::registry::{Address, ECKey, HDKey};
use crate::registry::{Address, ECKey, HDKeyRef};

/// Context type passed to [`Terminal`] [`minicbor::Decode`] implementation.
///
Expand Down Expand Up @@ -169,14 +169,14 @@ pub enum Key<'a> {
/// Elliptic-curve key.
ECKey(ECKey<'a>),
/// Elliptic-curve key with the derivation information.
HDKey(HDKey<'a>),
HDKey(HDKeyRef<'a>),
}

impl<'b, C> Decode<'b, C> for Key<'b> {
fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result<Self, Error> {
d.tag().and_then(|t| match t {
ECKey::TAG => ECKey::decode(d, ctx).map(Self::ECKey),
HDKey::TAG => HDKey::decode(d, ctx).map(Self::HDKey),
HDKeyRef::TAG => HDKeyRef::decode(d, ctx).map(Self::HDKey),
_ => Err(Error::message("invalid tag")),
})
}
Expand All @@ -194,7 +194,7 @@ impl<'a, C> Encode<C> for Key<'a> {
k.encode(e, ctx)
}
Key::HDKey(k) => {
e.tag(HDKey::TAG)?;
e.tag(HDKeyRef::TAG)?;
k.encode(e, ctx)
}
}
Expand Down
4 changes: 2 additions & 2 deletions urtypes/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ use core::fmt::{Display, Formatter};

use minicbor::{bytes::ByteSlice, encode::Write, Encode, Encoder};

use crate::registry::{HDKey, PassportRequest, PassportResponse};
use crate::registry::{HDKeyRef, PassportRequest, PassportResponse};

#[derive(Debug, PartialEq)]
pub enum Value<'a> {
/// bytes.
Bytes(&'a [u8]),
/// crypto-hdkey.
HDKey(HDKey<'a>),
HDKey(HDKeyRef<'a>),
/// crypto-psbt.
Psbt(&'a [u8]),
/// crypto-request for Passport.
Expand Down
Loading

0 comments on commit c7b4318

Please sign in to comment.