diff --git a/urtypes/Cargo.toml b/urtypes/Cargo.toml index e3ff3ad..ea9a222 100644 --- a/urtypes/Cargo.toml +++ b/urtypes/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "foundation-urtypes" -version = "0.4.1" +version = "0.5.0" edition = "2021" homepage.workspace = true description = """ diff --git a/urtypes/fuzz/fuzz_targets/hdkey_decode.rs b/urtypes/fuzz/fuzz_targets/hdkey_decode.rs index 82ee81c..7b9eaf0 100644 --- a/urtypes/fuzz/fuzz_targets/hdkey_decode.rs +++ b/urtypes/fuzz/fuzz_targets/hdkey_decode.rs @@ -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(); }); diff --git a/urtypes/src/registry/hdkey.rs b/urtypes/src/registry/hdkey.rs index bf6a337..6f04812 100644 --- a/urtypes/src/registry/hdkey.rs +++ b/urtypes/src/registry/hdkey.rs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. // SPDX-License-Identifier: GPL-3.0-or-later +#[cfg(feature = "alloc")] +use alloc::string::String; use core::num::NonZeroU32; use minicbor::{ @@ -8,25 +10,27 @@ use minicbor::{ 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 { @@ -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()), @@ -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 { 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()), @@ -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 { 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( @@ -116,19 +120,45 @@ impl<'b, C> Decode<'b, C> for HDKey<'b> { } } -impl<'a, C> Encode for HDKey<'a> { +impl<'a, C> Encode for HDKeyRef<'a> { fn encode( &self, e: &mut Encoder, ctx: &mut C, ) -> Result<(), minicbor::encode::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> 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 { + HDKeyRef::decode(d, ctx).map(HDKey::from) + } +} + /// A master key. #[doc(alias("master-key"))] #[derive(Debug, Clone, Eq, PartialEq)] @@ -206,10 +236,10 @@ impl Encode 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. @@ -219,9 +249,9 @@ pub struct DerivedKey<'a> { /// How the key is to be used. pub use_info: Option, /// How the key was derived. - pub origin: Option>, + pub origin: Option>, /// What children should/can be derived from this. - pub children: Option>, + pub children: Option>, /// The fingerprint of this key's direct ancestor. pub parent_fingerprint: Option, /// A short name for this key. @@ -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 { let mut is_private = false; let mut key_data = None; @@ -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 => { @@ -299,7 +329,7 @@ impl<'b, C> Decode<'b, C> for DerivedKey<'b> { } } -impl<'a, C> Encode for DerivedKey<'a> { +impl<'a, C> Encode for DerivedKeyRef<'a> { fn encode( &self, e: &mut Encoder, @@ -357,3 +387,45 @@ impl<'a, C> Encode 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, + /// How the key was derived. + pub origin: Option, + /// What children should/can be derived from this. + pub children: Option, + /// The fingerprint of this key's direct ancestor. + pub parent_fingerprint: Option, + /// A short name for this key. + pub name: Option, + /// An arbitrary amount of text describing the key. + pub note: Option, +} + +#[cfg(feature = "alloc")] +impl<'a> From> 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), + } + } +} diff --git a/urtypes/src/registry/keypath.rs b/urtypes/src/registry/keypath.rs index f7a5de6..4e3a24f 100644 --- a/urtypes/src/registry/keypath.rs +++ b/urtypes/src/registry/keypath.rs @@ -1,14 +1,17 @@ // SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. // 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. @@ -17,7 +20,7 @@ pub struct Keypath<'a> { pub depth: Option, } -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. @@ -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 { let mut components = None; let mut source_fingerprint = None; @@ -71,7 +74,7 @@ impl<'b, C> Decode<'b, C> for Keypath<'b> { } } -impl<'a, C> Encode for Keypath<'a> { +impl<'a, C> Encode for KeypathRef<'a> { fn encode( &self, e: &mut Encoder, @@ -97,7 +100,7 @@ impl<'a, C> Encode 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 { @@ -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, + /// Fingerprint from the ancestor key. + pub source_fingerprint: Option, + /// How many derivations this key is from the master (which is 0). + pub depth: Option, +} + +#[cfg(feature = "alloc")] +impl<'a> From> 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> { diff --git a/urtypes/src/registry/output_descriptor.rs b/urtypes/src/registry/output_descriptor.rs index 53b1416..088b940 100644 --- a/urtypes/src/registry/output_descriptor.rs +++ b/urtypes/src/registry/output_descriptor.rs @@ -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. /// @@ -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 { 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")), }) } @@ -194,7 +194,7 @@ impl<'a, C> Encode for Key<'a> { k.encode(e, ctx) } Key::HDKey(k) => { - e.tag(HDKey::TAG)?; + e.tag(HDKeyRef::TAG)?; k.encode(e, ctx) } } diff --git a/urtypes/src/value.rs b/urtypes/src/value.rs index 7c3e024..20b8c8f 100644 --- a/urtypes/src/value.rs +++ b/urtypes/src/value.rs @@ -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. diff --git a/urtypes/tests/hdkey.rs b/urtypes/tests/hdkey.rs index 3e5a776..298444d 100644 --- a/urtypes/tests/hdkey.rs +++ b/urtypes/tests/hdkey.rs @@ -2,27 +2,27 @@ // SPDX-License-Identifier: GPL-3.0-or-later use foundation_test_vectors::{HDKeyVector, URVector, UR}; -use foundation_urtypes::registry::{HDKey, Keypath}; +use foundation_urtypes::registry::{HDKeyRef, KeypathRef}; #[test] -fn test_roundtrip() { +fn test_roundtrip_ref() { let vectors = URVector::new(); for vector in vectors.iter().filter(|v| matches!(v.ur, UR::HDKey(_))) { let hdkey = match vector.ur.unwrap_hdkey() { HDKeyVector::Xpub { key, origin } => { - let mut hdkey = HDKey::try_from(key).unwrap(); + let mut hdkey = HDKeyRef::try_from(key).unwrap(); match hdkey { - HDKey::DerivedKey(ref mut derived_key) => { - derived_key.origin = origin.as_ref().map(Keypath::from); + HDKeyRef::DerivedKey(ref mut derived_key) => { + derived_key.origin = origin.as_ref().map(KeypathRef::from); } _ => unreachable!(), } hdkey } - HDKeyVector::Xprv { key } => HDKey::try_from(key).unwrap(), + HDKeyVector::Xprv { key } => HDKeyRef::try_from(key).unwrap(), }; let cbor = minicbor::to_vec(&hdkey).unwrap();