Skip to content

Commit e41ecea

Browse files
Merge pull request #230 from iqlusioninc/hkd32/bech32
hkd32: Add (optional) Bech32 encoding support
2 parents cdd520a + a1dfaf0 commit e41ecea

File tree

6 files changed

+84
-3
lines changed

6 files changed

+84
-3
lines changed

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hkd32/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ travis-ci = { repository = "iqlusioninc/crates", branch = "develop" }
2323
getrandom = { version = "0.1", optional = true }
2424
hmac = { version = "0.7", default-features = false }
2525
sha2 = { version = "0.8", default-features = false }
26+
subtle-encoding = { version = "0.3", optional = true }
2627
tiny-bip39 = { version = "0.6", default-features = false, optional = true }
2728

2829
[dependencies.zeroize]
@@ -32,8 +33,9 @@ default-features = false
3233
features = ["zeroize_derive"]
3334

3435
[features]
35-
default = ["alloc", "getrandom"]
36+
default = ["alloc", "bech32", "getrandom"]
3637
alloc = ["zeroize/alloc"]
38+
bech32 = ["alloc", "subtle-encoding/bech32-preview"]
3739
mnemonic = ["alloc", "tiny-bip39"]
3840

3941
[package.metadata.docs.rs]

hkd32/src/key_material.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99
#[cfg(feature = "mnemonic")]
1010
use crate::mnemonic;
1111
use crate::{path::Path, Error, KEY_SIZE};
12+
#[cfg(feature = "bech32")]
13+
use alloc::string::String;
1214
use core::convert::TryFrom;
1315
#[cfg(feature = "getrandom")]
1416
use getrandom::getrandom;
1517
use hmac::{Hmac, Mac};
1618
use sha2::Sha512;
19+
#[cfg(feature = "bech32")]
20+
use subtle_encoding::bech32::Bech32;
1721
use zeroize::Zeroize;
22+
#[cfg(feature = "bech32")]
23+
use zeroize::Zeroizing;
1824

1925
/// Cryptographic key material: 256-bit (32-byte) uniformly random bytestring
2026
/// generated either via a CSRNG or via hierarchical derivation.
@@ -34,6 +40,18 @@ impl KeyMaterial {
3440
Self::new(bytes)
3541
}
3642

43+
/// Decode key material from a Bech32 representation
44+
#[cfg(feature = "bech32")]
45+
pub fn from_bech32<S>(encoded: S) -> Result<(String, Self), Error>
46+
where
47+
S: AsRef<str>,
48+
{
49+
let (hrp, mut key_bytes) = Bech32::default().decode(encoded).map_err(|_| Error)?;
50+
let key_result = Self::from_bytes(&key_bytes);
51+
key_bytes.zeroize();
52+
key_result.map(|key| (hrp, key))
53+
}
54+
3755
/// Create key material from a 24-word BIP39 mnemonic phrase
3856
#[cfg(feature = "mnemonic")]
3957
pub fn from_mnemonic<S>(phrase: S, language: mnemonic::Language) -> Result<Self, Error>
@@ -100,6 +118,16 @@ impl KeyMaterial {
100118
})
101119
}
102120

121+
/// Serialize this `KeyMaterial` as a self-zeroizing Bech32 string
122+
#[cfg(feature = "bech32")]
123+
pub fn to_bech32<S>(&self, hrp: S) -> Zeroizing<String>
124+
where
125+
S: AsRef<str>,
126+
{
127+
let b32 = Bech32::default().encode(hrp, self.as_bytes());
128+
Zeroizing::new(b32)
129+
}
130+
103131
/// Serialize this `KeyMaterial` as a BIP39 mnemonic phrase
104132
#[cfg(feature = "mnemonic")]
105133
pub fn to_mnemonic(&self, language: mnemonic::Language) -> mnemonic::Phrase {

hkd32/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,29 @@
66
//! This library implements a fully symmetric construction inspired by
77
//! [BIP-0032: Hierarchical Deterministic Wallets][bip32].
88
//!
9+
//! # Usage
10+
//!
11+
//! To derive a key using HKD32, you'll need the following:
12+
//!
13+
//! - `hkd32::KeyMaterial`: a 32-byte (256-bit) uniformly random value
14+
//! - `hkd32::Path` or `hkd32::PathBuf`: path to the child key
15+
//!
16+
//! Derivation paths can be raw bytestrings but also support
17+
//!
18+
//! # Example
19+
//!
20+
//! ```rust
21+
//! // Parent key
22+
//! let input_key_material = hkd32::KeyMaterial::random();
23+
//!
24+
//! // Path to the child key
25+
//! let derivation_path = "/foo/bar/baz".parse::<hkd32::PathBuf>().unwrap();
26+
//!
27+
//! // Derive subkey from the parent key. Call `as_bytes()` on this to obtain
28+
//! // a byte slice containing the derived key.
29+
//! let output_key_material = input_key_material.derive_subkey(derivation_path);
30+
//! ```
31+
//!
932
//! [bip32]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
1033
1134
#![no_std]

hkd32/src/path.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,15 @@ impl<'a> Component<'a> {
192192
/// Requires the `alloc` feature is enabled.
193193
#[cfg(feature = "alloc")]
194194
pub fn stringify(&self) -> Result<String, Error> {
195-
str::from_utf8(self.as_bytes())
195+
let s = str::from_utf8(self.as_bytes())
196196
.map(String::from)
197-
.map_err(|_| Error)
197+
.map_err(|_| Error)?;
198+
199+
if s.is_ascii() {
200+
Ok(s)
201+
} else {
202+
Err(Error)
203+
}
198204
}
199205

200206
/// Serialize this component as a length-prefixed bytestring.

hkd32/src/pathbuf.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ impl FromStr for PathBuf {
117117
}
118118

119119
for component in components {
120+
if !component.is_ascii() {
121+
return Err(Error);
122+
}
123+
120124
result.push(Component::new(component.as_bytes())?);
121125
}
122126

0 commit comments

Comments
 (0)