Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Slip32 #436

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ version = "0.1.0"
workspace = ".."

[dependencies]
bip39 = "*"
bip39 = "0.6.0-beta.1"
failure = "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
failure = "*"
failure = "0.1.5"

hmac = "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
hmac = "*"
hmac = "0.7.0"

secp256k1 = "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
secp256k1 = "*"
secp256k1 = "0.12.2"

sha2 = "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sha2 = "*"
sha2 = "0.8.0"

bech32 = "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bech32 = "*"
bech32 = "0.6.0"

tiny-hderive = "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tiny-hderive = "*"
tiny-hderive = "0.1.0"

endianrw = "*"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
endianrw = "*"
endianrw = "0.2.2"

155 changes: 155 additions & 0 deletions wallet/src/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! # BIP32 Key generation and derivation
//!
//! Example
//!
//! ```
//! # use witnet_wallet::mnemonic;
//! # use witnet_wallet::key;
//! let passphrase = "";
//! let seed = mnemonic::MnemonicGen::new().generate().seed(passphrase);
//! let ext_key = key::MasterKeyGen::new(seed).generate();
//! ```

use failure::Fail;
use hmac::{Hmac, Mac};
use secp256k1;
use sha2;
use tiny_hderive;

/// Default HMAC key used when generating a Master Key with
/// [generate_master](generate_master)
pub static DEFAULT_HMAC_KEY: &str = "Witnet seed";

/// BIP32 Key
pub trait Key {}

/// BIP32 Secret Key
pub struct SK(secp256k1::SecretKey);

struct SKSlip32Serializer;

/// BIP32 Public Key
pub struct PK(secp256k1::PublicKey);

/// BIP32 extended Secret Key
pub type ExtendedSK = ExtendedKey<SK>;

/// BIP32 extended Public Key
pub type ExtendedPK = ExtendedKey<PK>;

/// BIP32 chain code which is the entropy used in extended keys
pub type ChainCode = [u8; 32];

/// The path of a key inside an HD Wallet
pub struct KeyPath(tiny_hderive::bip44::DerivationPath);

/// A child number for a derived key
pub type ChildNumber = tiny_hderive::bip44::ChildNumber;

impl KeyPath {
/// Get the depth/level of the derived key
pub fn depth(&self) -> usize {
self.0.as_ref().len()
}

pub fn iter(&self) -> impl Iterator<Item = &ChildNumber> {
self.0.iter()
}
}

/// The error type for [generate_master](generate_master)
#[derive(Debug, PartialEq, Fail)]
pub enum MasterKeyGenError {
/// Invalid hmac key length
#[fail(display = "The length of the hmac key is invalid")]
InvalidKeyLength,
/// Invalid seed length
#[fail(display = "The length of the seed is invalid, must be between 128/256 bits")]
InvalidSeedLength,
}

/// BIP32 Master Secret Key generator
pub struct MasterKeyGen<'a, S> {
seed: S,
key: &'a str,
}

impl<'a, S> MasterKeyGen<'a, S>
where
S: AsRef<[u8]>,
{
/// Create a new master key generator
pub fn new(seed: S) -> Self {
Self {
key: DEFAULT_HMAC_KEY,
seed: seed,
}
}

/// Use the given key as the HMAC key
pub fn with_key(mut self, key: &'a str) -> Self {
self.key = key;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.key = key;
self.key = key;

self
}

/// Consume this generator and return the BIP32 extended Master Secret Key
/// [Extended Key](ExtendedSK)
pub fn generate(self) -> Result<ExtendedSK, MasterKeyGenError> {
let seed_bytes = self.seed.as_ref();
let seed_len = seed_bytes.len();

if seed_len < 16 || seed_len > 64 {
Err(MasterKeyGenError::InvalidSeedLength)?
}

let key_bytes = self.key.as_ref();
let mut mac = Hmac::<sha2::Sha512>::new_varkey(key_bytes)
.map_err(|_| MasterKeyGenError::InvalidKeyLength)?;
mac.input(seed_bytes);
let result = mac.result().code();
let (sk_bytes, chain_code_bytes) = result.split_at(32);

// secret/chain_code computation might panic if length returned by hmac is wrong
let secret =
SK(secp256k1::SecretKey::from_slice(sk_bytes).expect("Secret Key length error"));
let mut chain_code = [0u8; 32];
chain_code.copy_from_slice(chain_code_bytes);

Ok(ExtendedKey { secret, chain_code })
}
}

/// Extended Key is just a Key with a Chain Code
pub struct ExtendedKey<K> {
pub secret: K,
pub chain_code: ChainCode,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_generate_master_invalid_seed() {
let seed = "too short seed";
let result = MasterKeyGen::new(seed).generate();

assert!(result.is_err());
}

#[test]
fn test_generate_master() {
let seed = [0; 32];
let result = MasterKeyGen::new(seed).generate();

assert!(result.is_ok());
}

#[test]
fn test_generate_master_other_key() {
let seed = [0; 32];
let result = MasterKeyGen::new(seed).with_key("Bitcoin seed").generate();

assert!(result.is_ok());
}
}
4 changes: 4 additions & 0 deletions wallet/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
//! Wallet implementation for Witnet

pub mod key;
pub mod mnemonic;
pub mod slip32;
155 changes: 155 additions & 0 deletions wallet/src/mnemonic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! # BIP39 Mnemonic and Seed generation
//!
//! Example
//!
//! ```
//! # use witnet_wallet::mnemonic::MnemonicGen;
//! let mnemonic = MnemonicGen::new().generate();
//!
//! // A Mnemonic Seed must be protected by a passphrase
//! let passphrase = "";
//!
//! // String of mnemonic words
//! let words = mnemonic.words();
//! // Seed that can be used to generate a master secret key
//! let seed = mnemonic.seed(passphrase);
//! ```
use bip39;

/// BIP39 Mnemonic
pub struct Mnemonic(bip39::Mnemonic);

impl Mnemonic {
/// Get the list of mnemonic words
pub fn words(&self) -> &str {
self.0.phrase()
}

/// Get the binary seed used for generating a master secret key
pub fn seed<'a, S: Into<&'a str>>(&self, passphrase: S) -> Seed {
Seed(bip39::Seed::new(&self.0, passphrase.into()))
}
}

/// BIP39 Seed generated from a Mnemonic
pub struct Seed(bip39::Seed);

impl Seed {
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}

impl AsRef<[u8]> for Seed {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}

/// Number of words of the Mnemonic
///
/// The number of words of the Mnemonic is proportional to the
/// entropy:
///
/// * `128 bits` generates `12 words` mnemonic
/// * `160 bits` generates `15 words` mnemonic
/// * `192 bits` generates `18 words` mnemonic
/// * `224 bits` generates `21 words` mnemonic
/// * `256 bits` generates `24 words` mnemonic
#[derive(Debug, PartialEq)]
pub enum Length {
Words12,
Words15,
Words18,
Words21,
Words24,
}

/// The language in which Mnemonics are generated
#[derive(Debug, PartialEq)]
pub enum Lang {
English,
}

/// BIP39 Mnemonic generator
pub struct MnemonicGen {
len: Length,
lang: Lang,
}

impl MnemonicGen {
/// Create a new BIP39 Mnemonic generator
pub fn new() -> Self {
MnemonicGen {
len: Length::Words12,
lang: Lang::English,
}
}

/// Set how many words the Mnemonic should have
pub fn with_len(mut self, len: Length) -> Self {
self.len = len;
self
}

/// Set which language to use in the Mnemonic words
pub fn with_lang(mut self, lang: Lang) -> Self {
self.lang = lang;
self
}

/// Consume this generator and return the BIP39 Mnemonic
pub fn generate(self) -> Mnemonic {
let mnemonic_type = match self.len {
Length::Words12 => bip39::MnemonicType::Words12,
Length::Words15 => bip39::MnemonicType::Words15,
Length::Words18 => bip39::MnemonicType::Words18,
Length::Words21 => bip39::MnemonicType::Words21,
Length::Words24 => bip39::MnemonicType::Words24,
};
let lang = match self.lang {
Lang::English => bip39::Language::English,
};
let mnemonic = bip39::Mnemonic::new(mnemonic_type, lang);

Mnemonic(mnemonic)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_gen_default() {
let gen = MnemonicGen::new();

assert_eq!(gen.len, Length::Words12);
assert_eq!(gen.lang, Lang::English);
}

#[test]
fn test_gen_with_len() {
let gen = MnemonicGen::new().with_len(Length::Words24);

assert_eq!(gen.len, Length::Words24);
assert_eq!(gen.lang, Lang::English);
}

#[test]
fn test_generate() {
let mnemonic = MnemonicGen::new().generate();
let words: Vec<&str> = mnemonic.words().split_whitespace().collect();

assert_eq!(words.len(), 12);
}

#[test]
fn test_seed_as_ref() {
let mnemonic = MnemonicGen::new().generate();
let seed = mnemonic.seed("");
let bytes: &[u8] = seed.as_ref();

assert_eq!(bytes, seed.as_bytes());
}
}
68 changes: 68 additions & 0 deletions wallet/src/slip32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//! Slip32 implementation

use crate::key;

/// Byte sizes for each field used in the serialization format
// pub struct SerializationFormat {
// version: u8,
// depth: u8,
// fingerpint: u8,
// child_number: u8,
// chain_code: u8,
// key: u8
// }

/// Export a BIP32 extended key to the SLIP32 format
pub fn export<K>(path: key::KeyPath, extkey: key::ExtendedKey<K>) -> String
where
K: key::Key,
{
let depth = path.depth();
let mut buffer = Vec::with_capacity(buffer_size(depth));

// 1 byte for depth
buffer[0] = depth as u8;

// 4 bytes for each child number in the derivation path
for (level, child_number) in path.iter().enumerate() {
let start = 1 + level * 4;
let end = start + 4;
buffer.splice(start..end, child_number.to_bytes().iter().cloned());
}

// 32 bytes for chain code
{
let start = 1 + depth * 4;
let end = start + 32;
buffer.splice(start..end, extkey.chain_code.iter().cloned());
}

// 33 bytes for key
//
// TODO: Find a way to get access to secp256k1::SecretKey bytes,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In rust-bitcoin/rust-secp256k1#75 (comment) they answer how to get the bytes out from a secret key.

You should be able to use [..] to get the bytes out of a secret key no problem. See, eg, this line in rust-lightning: https://github.com/rust-bitcoin/rust-lightning/blob/master/src/ln/chan_utils.rs#L80

// at the moment it requires creating a custom serde Serializer
{
let start = 33 + depth * 4;
let end = start + 33;
// buffer.splice(start..end, extkey.key...
}

unimplemented!()
}

/// Import a BIP32 extended key in the SLIP32 format
pub fn import<K>(exported: String) -> key::ExtendedKey<K>
where
K: key::Key,
{
unimplemented!()
}

/// Calculate the size of the buffer that will contain the serialized key.
/// 1 byte: depth
/// 32 bytes: chain code
/// 33 bytes: the key
/// 4 bytes for each key in the keypath
fn buffer_size(depth: usize) -> usize {
66 + depth * 4
}