-
Notifications
You must be signed in to change notification settings - Fork 56
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
[WIP] Slip32 #436
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -6,5 +6,11 @@ version = "0.1.0" | |||||
workspace = ".." | ||||||
|
||||||
[dependencies] | ||||||
bip39 = "*" | ||||||
bip39 = "0.6.0-beta.1" | ||||||
failure = "*" | ||||||
hmac = "*" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
secp256k1 = "*" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
sha2 = "*" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
bech32 = "*" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
tiny-hderive = "*" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
endianrw = "*" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
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; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
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()); | ||||||||
} | ||||||||
} |
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; |
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()); | ||
} | ||
} |
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
|
||
// 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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.