From 259259eabf1a79b096931462b400d992c767326f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 10 Sep 2020 02:37:10 +0200 Subject: [PATCH 1/4] BIP 32 binary encoding functions are extracted from base58 --- src/util/bip32.rs | 195 +++++++++++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 73 deletions(-) diff --git a/src/util/bip32.rs b/src/util/bip32.rs index ad179be8..df144ae1 100644 --- a/src/util/bip32.rs +++ b/src/util/bip32.rs @@ -388,6 +388,12 @@ pub enum Error { InvalidChildNumberFormat, /// Invalid derivation path format. InvalidDerivationPathFormat, + /// Unknown version magic bytes + UnknownVersion([u8; 4]), + /// Encoded extended key data have wrong length + WrongExtendedKeyLength(usize), + /// Base58 encoding error + Base58(base58::Error) } impl fmt::Display for Error { @@ -399,6 +405,9 @@ impl fmt::Display for Error { Error::RngError(ref s) => write!(f, "rng error {}", s), Error::InvalidChildNumberFormat => f.write_str("invalid child number format"), Error::InvalidDerivationPathFormat => f.write_str("invalid derivation path format"), + Error::UnknownVersion(ref bytes) => write!(f, "unknown version magic bytes: {:?}", bytes), + Error::WrongExtendedKeyLength(ref len) => write!(f, "encoded extended key data have wrong length {}", len), + Error::Base58(ref err) => write!(f, "base58 encoding error: {}", err), } } } @@ -417,6 +426,18 @@ impl From for Error { fn from(e: secp256k1::Error) -> Error { Error::Ecdsa(e) } } +impl From for Error { + fn from(err: base58::Error) -> Self { + Error::Base58(err) + } +} + +impl From for base58::Error { + fn from(err: Error) -> Self { + base58::Error::Other(err.to_string()) + } +} + impl ExtendedPrivKey { /// Construct a new master key from a seed value pub fn new_master(network: Network, seed: &[u8]) -> Result { @@ -459,11 +480,11 @@ impl ExtendedPrivKey { pub fn ckd_priv(&self, secp: &Secp256k1, i: ChildNumber) -> Result { let mut hmac_engine: HmacEngine = HmacEngine::new(&self.chain_code[..]); match i { - ChildNumber::Normal {..} => { + ChildNumber::Normal { .. } => { // Non-hardened key: compute public data and use that hmac_engine.input(&PublicKey::from_private_key(secp, &self.private_key).key.serialize()[..]); } - ChildNumber::Hardened {..} => { + ChildNumber::Hardened { .. } => { // Hardened key: use only secret data to prevent public derivation hmac_engine.input(&[0u8]); hmac_engine.input(&self.private_key[..]); @@ -489,6 +510,57 @@ impl ExtendedPrivKey { }) } + /// Decoding extended private key from binary data according to BIP 32 + pub fn decode(data: &[u8]) -> Result { + if data.len() != 78 { + return Err(Error::WrongExtendedKeyLength(data.len())) + } + + let cn_int: u32 = endian::slice_to_u32_be(&data[9..13]); + let child_number: ChildNumber = ChildNumber::from(cn_int); + + let network = if data[0..4] == [0x04u8, 0x88, 0xAD, 0xE4] { + Network::Bitcoin + } else if data[0..4] == [0x04u8, 0x35, 0x83, 0x94] { + Network::Testnet + } else { + return Err(base58::Error::InvalidVersion((&data[0..4]).to_vec()).into()); + }; + + Ok(ExtendedPrivKey { + network: network, + depth: data[4], + parent_fingerprint: Fingerprint::from(&data[5..9]), + child_number: child_number, + chain_code: ChainCode::from(&data[13..45]), + private_key: PrivateKey { + compressed: true, + network: network, + key: secp256k1::SecretKey::from_slice( + &data[46..78] + ).map_err(|e| + base58::Error::Other(e.to_string()) + )?, + }, + }) + } + + /// Extended private key binary encoding according to BIP 32 + pub fn encode(&self) -> [u8; 78] { + let mut ret = [0; 78]; + ret[0..4].copy_from_slice(&match self.network { + Network::Bitcoin => [0x04, 0x88, 0xAD, 0xE4], + Network::Testnet | Network::Signet | Network::Regtest => [0x04, 0x35, 0x83, 0x94], + }[..]); + ret[4] = self.depth as u8; + ret[5..9].copy_from_slice(&self.parent_fingerprint[..]); + ret[9..13].copy_from_slice(&endian::u32_to_array_be(u32::from(self.child_number))); + ret[13..45].copy_from_slice(&self.chain_code[..]); + ret[45] = 0; + ret[46..78].copy_from_slice(&self.private_key[..]); + ret + } + /// Returns the HASH160 of the public key belonging to the xpriv pub fn identifier(&self, secp: &Secp256k1) -> XpubIdentifier { ExtendedPubKey::from_private(secp, self).identifier() @@ -531,7 +603,7 @@ impl ExtendedPubKey { /// Compute the scalar tweak added to this key to get a child key pub fn ckd_pub_tweak(&self, i: ChildNumber) -> Result<(PrivateKey, ChainCode), Error> { match i { - ChildNumber::Hardened {..} => { + ChildNumber::Hardened { .. } => { Err(Error::CannotDeriveFromHardenedKey) } ChildNumber::Normal { index: n } => { @@ -572,7 +644,49 @@ impl ExtendedPubKey { }) } - /// Returns the HASH160 of the public key of the xpub + /// Decoding extended public key from binary data according to BIP 32 + pub fn decode(data: &[u8]) -> Result { + if data.len() != 78 { + return Err(Error::WrongExtendedKeyLength(data.len())) + } + + let cn_int: u32 = endian::slice_to_u32_be(&data[9..13]); + let child_number: ChildNumber = ChildNumber::from(cn_int); + + Ok(ExtendedPubKey { + network: if data[0..4] == [0x04u8, 0x88, 0xB2, 0x1E] { + Network::Bitcoin + } else if data[0..4] == [0x04u8, 0x35, 0x87, 0xCF] { + Network::Testnet + } else { + return Err(base58::Error::InvalidVersion((&data[0..4]).to_vec()).into()); + }, + depth: data[4], + parent_fingerprint: Fingerprint::from(&data[5..9]), + child_number: child_number, + chain_code: ChainCode::from(&data[13..45]), + public_key: PublicKey::from_slice( + &data[45..78]).map_err(|e| + base58::Error::Other(e.to_string()))?, + }) + } + + /// Extended public key binary encoding according to BIP 32 + pub fn encode(&self) -> [u8; 78] { + let mut ret = [0; 78]; + ret[0..4].copy_from_slice(&match self.network { + Network::Bitcoin => [0x04u8, 0x88, 0xB2, 0x1E], + Network::Testnet | Network::Signet | Network::Regtest => [0x04u8, 0x35, 0x87, 0xCF], + }[..]); + ret[4] = self.depth as u8; + ret[5..9].copy_from_slice(&self.parent_fingerprint[..]); + ret[9..13].copy_from_slice(&endian::u32_to_array_be(u32::from(self.child_number))); + ret[13..45].copy_from_slice(&self.chain_code[..]); + ret[45..78].copy_from_slice(&self.public_key.key.serialize()[..]); + ret + } + + /// Returns the HASH160 of the chaincode pub fn identifier(&self) -> XpubIdentifier { let mut engine = XpubIdentifier::engine(); self.public_key.write_into(&mut engine).expect("engines don't error"); @@ -587,18 +701,7 @@ impl ExtendedPubKey { impl fmt::Display for ExtendedPrivKey { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let mut ret = [0; 78]; - ret[0..4].copy_from_slice(&match self.network { - Network::Bitcoin => [0x04, 0x88, 0xAD, 0xE4], - Network::Testnet | Network::Signet | Network::Regtest => [0x04, 0x35, 0x83, 0x94], - }[..]); - ret[4] = self.depth as u8; - ret[5..9].copy_from_slice(&self.parent_fingerprint[..]); - ret[9..13].copy_from_slice(&endian::u32_to_array_be(u32::from(self.child_number))); - ret[13..45].copy_from_slice(&self.chain_code[..]); - ret[45] = 0; - ret[46..78].copy_from_slice(&self.private_key[..]); - fmt.write_str(&base58::check_encode_slice(&ret[..])) + fmt.write_str(&base58::check_encode_slice(&self.encode()[..])) } } @@ -612,49 +715,13 @@ impl FromStr for ExtendedPrivKey { return Err(base58::Error::InvalidLength(data.len())); } - let cn_int: u32 = endian::slice_to_u32_be(&data[9..13]); - let child_number: ChildNumber = ChildNumber::from(cn_int); - - let network = if data[0..4] == [0x04u8, 0x88, 0xAD, 0xE4] { - Network::Bitcoin - } else if data[0..4] == [0x04u8, 0x35, 0x83, 0x94] { - Network::Testnet - } else { - return Err(base58::Error::InvalidVersion((&data[0..4]).to_vec())); - }; - - Ok(ExtendedPrivKey { - network: network, - depth: data[4], - parent_fingerprint: Fingerprint::from(&data[5..9]), - child_number: child_number, - chain_code: ChainCode::from(&data[13..45]), - private_key: PrivateKey { - compressed: true, - network: network, - key: secp256k1::SecretKey::from_slice( - &data[46..78] - ).map_err(|e| - base58::Error::Other(e.to_string()) - )?, - }, - }) + Ok(ExtendedPrivKey::decode(&data[..])?) } } impl fmt::Display for ExtendedPubKey { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let mut ret = [0; 78]; - ret[0..4].copy_from_slice(&match self.network { - Network::Bitcoin => [0x04u8, 0x88, 0xB2, 0x1E], - Network::Testnet | Network::Signet | Network::Regtest => [0x04u8, 0x35, 0x87, 0xCF], - }[..]); - ret[4] = self.depth as u8; - ret[5..9].copy_from_slice(&self.parent_fingerprint[..]); - ret[9..13].copy_from_slice(&endian::u32_to_array_be(u32::from(self.child_number))); - ret[13..45].copy_from_slice(&self.chain_code[..]); - ret[45..78].copy_from_slice(&self.public_key.key.serialize()[..]); - fmt.write_str(&base58::check_encode_slice(&ret[..])) + fmt.write_str(&base58::check_encode_slice(&self.encode()[..])) } } @@ -668,25 +735,7 @@ impl FromStr for ExtendedPubKey { return Err(base58::Error::InvalidLength(data.len())); } - let cn_int: u32 = endian::slice_to_u32_be(&data[9..13]); - let child_number: ChildNumber = ChildNumber::from(cn_int); - - Ok(ExtendedPubKey { - network: if data[0..4] == [0x04u8, 0x88, 0xB2, 0x1E] { - Network::Bitcoin - } else if data[0..4] == [0x04u8, 0x35, 0x87, 0xCF] { - Network::Testnet - } else { - return Err(base58::Error::InvalidVersion((&data[0..4]).to_vec())); - }, - depth: data[4], - parent_fingerprint: Fingerprint::from(&data[5..9]), - child_number: child_number, - chain_code: ChainCode::from(&data[13..45]), - public_key: PublicKey::from_slice( - &data[45..78]).map_err(|e| - base58::Error::Other(e.to_string()))? - }) + Ok(ExtendedPubKey::decode(&data[..])?) } } From d8028723107b09bdb257328d2ee9b666da6d9f67 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 6 Nov 2020 20:53:03 +0100 Subject: [PATCH 2/4] Improvements to extended keys encoding logic end errors --- src/util/bip32.rs | 49 ++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/util/bip32.rs b/src/util/bip32.rs index df144ae1..ba536f8c 100644 --- a/src/util/bip32.rs +++ b/src/util/bip32.rs @@ -27,7 +27,7 @@ use secp256k1::{self, Secp256k1}; use network::constants::Network; use util::{base58, endian}; -use util::key::{PublicKey, PrivateKey}; +use util::key::{self, PublicKey, PrivateKey}; /// A chain code #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -422,6 +422,15 @@ impl error::Error for Error { } } +impl From for Error { + fn from(err: key::Error) -> Self { + match err { + key::Error::Base58(e) => Error::Base58(e), + key::Error::Secp256k1(e) => Error::Ecdsa(e), + } + } +} + impl From for Error { fn from(e: secp256k1::Error) -> Error { Error::Ecdsa(e) } } @@ -432,12 +441,6 @@ impl From for Error { } } -impl From for base58::Error { - fn from(err: Error) -> Self { - base58::Error::Other(err.to_string()) - } -} - impl ExtendedPrivKey { /// Construct a new master key from a seed value pub fn new_master(network: Network, seed: &[u8]) -> Result { @@ -516,31 +519,28 @@ impl ExtendedPrivKey { return Err(Error::WrongExtendedKeyLength(data.len())) } - let cn_int: u32 = endian::slice_to_u32_be(&data[9..13]); - let child_number: ChildNumber = ChildNumber::from(cn_int); - let network = if data[0..4] == [0x04u8, 0x88, 0xAD, 0xE4] { Network::Bitcoin } else if data[0..4] == [0x04u8, 0x35, 0x83, 0x94] { Network::Testnet } else { - return Err(base58::Error::InvalidVersion((&data[0..4]).to_vec()).into()); + let mut ver = [0u8; 4]; + ver.copy_from_slice(&data[0..4]); + return Err(Error::UnknownVersion(ver)); }; Ok(ExtendedPrivKey { network: network, depth: data[4], parent_fingerprint: Fingerprint::from(&data[5..9]), - child_number: child_number, + child_number: endian::slice_to_u32_be(&data[9..13]).into(), chain_code: ChainCode::from(&data[13..45]), private_key: PrivateKey { compressed: true, network: network, key: secp256k1::SecretKey::from_slice( &data[46..78] - ).map_err(|e| - base58::Error::Other(e.to_string()) - )?, + ).map_err(Error::Ecdsa)?, }, }) } @@ -659,15 +659,16 @@ impl ExtendedPubKey { } else if data[0..4] == [0x04u8, 0x35, 0x87, 0xCF] { Network::Testnet } else { - return Err(base58::Error::InvalidVersion((&data[0..4]).to_vec()).into()); + let mut ver = [0u8; 4]; + ver.copy_from_slice(&data[0..4]); + return Err(Error::UnknownVersion(ver)); }, depth: data[4], parent_fingerprint: Fingerprint::from(&data[5..9]), child_number: child_number, chain_code: ChainCode::from(&data[13..45]), public_key: PublicKey::from_slice( - &data[45..78]).map_err(|e| - base58::Error::Other(e.to_string()))?, + &data[45..78])?, }) } @@ -706,13 +707,13 @@ impl fmt::Display for ExtendedPrivKey { } impl FromStr for ExtendedPrivKey { - type Err = base58::Error; + type Err = Error; - fn from_str(inp: &str) -> Result { + fn from_str(inp: &str) -> Result { let data = base58::from_check(inp)?; if data.len() != 78 { - return Err(base58::Error::InvalidLength(data.len())); + return Err(base58::Error::InvalidLength(data.len()).into()); } Ok(ExtendedPrivKey::decode(&data[..])?) @@ -726,13 +727,13 @@ impl fmt::Display for ExtendedPubKey { } impl FromStr for ExtendedPubKey { - type Err = base58::Error; + type Err = Error; - fn from_str(inp: &str) -> Result { + fn from_str(inp: &str) -> Result { let data = base58::from_check(inp)?; if data.len() != 78 { - return Err(base58::Error::InvalidLength(data.len())); + return Err(base58::Error::InvalidLength(data.len()).into()); } Ok(ExtendedPubKey::decode(&data[..])?) From a802ca88ef744e89bcff4b22fd449572a50dded0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 8 Nov 2020 18:29:30 +0100 Subject: [PATCH 3/4] Using base58::check_encode_slice_to_fmt for BIP32 encodings --- src/util/bip32.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/bip32.rs b/src/util/bip32.rs index ba536f8c..10bdfdc5 100644 --- a/src/util/bip32.rs +++ b/src/util/bip32.rs @@ -702,7 +702,7 @@ impl ExtendedPubKey { impl fmt::Display for ExtendedPrivKey { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(&base58::check_encode_slice(&self.encode()[..])) + base58::check_encode_slice_to_fmt(fmt, &self.encode()[..]) } } @@ -722,7 +722,7 @@ impl FromStr for ExtendedPrivKey { impl fmt::Display for ExtendedPubKey { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(&base58::check_encode_slice(&self.encode()[..])) + base58::check_encode_slice_to_fmt(fmt, &self.encode()[..]) } } From fa4ecb4a6a8cab36bef92a0e792cdddd5b395c1a Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 10 Nov 2020 09:29:24 +0100 Subject: [PATCH 4/4] Making ExtendedPubKey decode aligned with ExtendedPrivKey variant --- src/util/bip32.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/util/bip32.rs b/src/util/bip32.rs index 10bdfdc5..5611d78d 100644 --- a/src/util/bip32.rs +++ b/src/util/bip32.rs @@ -650,9 +650,6 @@ impl ExtendedPubKey { return Err(Error::WrongExtendedKeyLength(data.len())) } - let cn_int: u32 = endian::slice_to_u32_be(&data[9..13]); - let child_number: ChildNumber = ChildNumber::from(cn_int); - Ok(ExtendedPubKey { network: if data[0..4] == [0x04u8, 0x88, 0xB2, 0x1E] { Network::Bitcoin @@ -665,10 +662,9 @@ impl ExtendedPubKey { }, depth: data[4], parent_fingerprint: Fingerprint::from(&data[5..9]), - child_number: child_number, + child_number: endian::slice_to_u32_be(&data[9..13]).into(), chain_code: ChainCode::from(&data[13..45]), - public_key: PublicKey::from_slice( - &data[45..78])?, + public_key: PublicKey::from_slice(&data[45..78])?, }) }