Skip to content

Commit

Permalink
SLIP 32 extended key support (Y/Z/y/z/T/U/V/u/v)
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Nov 28, 2020
1 parent 438ec8f commit 5a17598
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 16 deletions.
4 changes: 2 additions & 2 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub use asset::AssetGenesis;
pub use descriptor_params::DescriptorParams;
pub use document::{Document, Error, Profile};
pub use tracking::{
DerivationComponents, DerivationRange, HardenedNormalSplit,
TrackingAccount, TrackingKey,
DerivationComponents, DerivationRange, Error as Slip32Error, FromSlip32,
HardenedNormalSplit, TrackingAccount, TrackingKey,
};
pub use utxo::UtxoEntry;
143 changes: 142 additions & 1 deletion src/model/tracking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ use std::iter::FromIterator;
use std::ops::RangeInclusive;

use amplify::Wrapper;
use lnpbp::bitcoin::util::base58;
use lnpbp::bitcoin::util::bip32::{
ChildNumber, DerivationPath, ExtendedPubKey,
self, ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey,
};
use lnpbp::bp::bip32::Decode;
use lnpbp::secp256k1;
use lnpbp::strict_encoding::{self, StrictDecode, StrictEncode};

Expand Down Expand Up @@ -63,6 +65,145 @@ impl TrackingKey {
}

// TODO: Consider moving the rest of the file to LNP/BP Core library

/// Extended public and private key processing errors
#[derive(Copy, Clone, PartialEq, Eq, Debug, Display, From, Error)]
#[display(doc_comments)]
pub enum Error {
/// Error in BASE58 key encoding
#[from(base58::Error)]
Base58,

/// A pk->pk derivation was attempted on a hardened key
CannotDeriveFromHardenedKey,

/// A child number was provided ({0}) that was out of range
InvalidChildNumber(u32),

/// Invalid child number format.
InvalidChildNumberFormat,

/// Invalid derivation path format.
InvalidDerivationPathFormat,

/// Unrecognized or unsupported extended key prefix (please check SLIP 32
/// for possible values)
UnknownSlip32Prefix,

/// Failure in tust bitcoin library
InteralFailure,
}

impl From<bip32::Error> for Error {
fn from(err: bip32::Error) -> Self {
match err {
bip32::Error::CannotDeriveFromHardenedKey => {
Error::CannotDeriveFromHardenedKey
}
bip32::Error::InvalidChildNumber(no) => {
Error::InvalidChildNumber(no)
}
bip32::Error::InvalidChildNumberFormat => {
Error::InvalidChildNumberFormat
}
bip32::Error::InvalidDerivationPathFormat => {
Error::InvalidDerivationPathFormat
}
bip32::Error::Ecdsa(_) | bip32::Error::RngError(_) => {
Error::InteralFailure
}
}
}
}

pub trait FromSlip32 {
fn from_slip32_str(s: &str) -> Result<Self, Error>
where
Self: Sized;
}

impl FromSlip32 for ExtendedPubKey {
fn from_slip32_str(s: &str) -> Result<Self, Error> {
const VERSION_MAGIC_XPUB: [u8; 4] = [0x04, 0x88, 0xB2, 0x1E];
const VERSION_MAGIC_YPUB: [u8; 4] = [0x04, 0x9D, 0x7C, 0xB2];
const VERSION_MAGIC_ZPUB: [u8; 4] = [0x04, 0xB2, 0x47, 0x46];
const VERSION_MAGIC_YPUB_MULTISIG: [u8; 4] = [0x02, 0x95, 0xb4, 0x3f];
const VERSION_MAGIC_ZPUB_MULTISIG: [u8; 4] = [0x02, 0xaa, 0x7e, 0xd3];

const VERSION_MAGIC_TPUB: [u8; 4] = [0x04, 0x35, 0x87, 0xCF];
const VERSION_MAGIC_UPUB: [u8; 4] = [0x04, 0x4A, 0x52, 0x62];
const VERSION_MAGIC_VPUB: [u8; 4] = [0x04, 0x5F, 0x1C, 0xF6];
const VERSION_MAGIC_UPUB_MULTISIG: [u8; 4] = [0x02, 0x42, 0x89, 0xef];
const VERSION_MAGIC_VPUB_MULTISIG: [u8; 4] = [0x02, 0x57, 0x54, 0x83];

let mut data = base58::from_check(s)?;

let mut prefix = [0u8; 4];
prefix.copy_from_slice(&data[0..4]);
let slice = match prefix {
VERSION_MAGIC_XPUB
| VERSION_MAGIC_YPUB
| VERSION_MAGIC_ZPUB
| VERSION_MAGIC_YPUB_MULTISIG
| VERSION_MAGIC_ZPUB_MULTISIG => VERSION_MAGIC_XPUB,

VERSION_MAGIC_TPUB
| VERSION_MAGIC_UPUB
| VERSION_MAGIC_VPUB
| VERSION_MAGIC_UPUB_MULTISIG
| VERSION_MAGIC_VPUB_MULTISIG => VERSION_MAGIC_TPUB,

_ => Err(Error::UnknownSlip32Prefix)?,
};
data[0..4].copy_from_slice(&slice);

let xpub = ExtendedPubKey::decode(&data)?;

Ok(xpub)
}
}

impl FromSlip32 for ExtendedPrivKey {
fn from_slip32_str(s: &str) -> Result<Self, Error> {
const VERSION_MAGIC_XPRV: [u8; 4] = [0x04, 0x88, 0xAD, 0xE4];
const VERSION_MAGIC_YPRV: [u8; 4] = [0x04, 0x9D, 0x78, 0x78];
const VERSION_MAGIC_ZPRV: [u8; 4] = [0x04, 0xB2, 0x43, 0x0C];
const VERSION_MAGIC_YPRV_MULTISIG: [u8; 4] = [0x02, 0x95, 0xb0, 0x05];
const VERSION_MAGIC_ZPRV_MULTISIG: [u8; 4] = [0x02, 0xaa, 0x7a, 0x99];

const VERSION_MAGIC_TPRV: [u8; 4] = [0x04, 0x35, 0x83, 0x94];
const VERSION_MAGIC_UPRV: [u8; 4] = [0x04, 0x4A, 0x4E, 0x28];
const VERSION_MAGIC_VPRV: [u8; 4] = [0x04, 0x5F, 0x18, 0xBC];
const VERSION_MAGIC_UPRV_MULTISIG: [u8; 4] = [0x02, 0x42, 0x85, 0xb5];
const VERSION_MAGIC_VPRV_MULTISIG: [u8; 4] = [0x02, 0x57, 0x50, 0x48];

let mut data = base58::from_check(s)?;

let mut prefix = [0u8; 4];
prefix.copy_from_slice(&data[0..4]);
let slice = match prefix {
VERSION_MAGIC_XPRV
| VERSION_MAGIC_YPRV
| VERSION_MAGIC_ZPRV
| VERSION_MAGIC_YPRV_MULTISIG
| VERSION_MAGIC_ZPRV_MULTISIG => VERSION_MAGIC_XPRV,

VERSION_MAGIC_TPRV
| VERSION_MAGIC_UPRV
| VERSION_MAGIC_VPRV
| VERSION_MAGIC_UPRV_MULTISIG
| VERSION_MAGIC_VPRV_MULTISIG => VERSION_MAGIC_TPRV,

_ => Err(Error::UnknownSlip32Prefix)?,
};
data[0..4].copy_from_slice(&slice);

let xprv = ExtendedPrivKey::decode(&data)?;

Ok(xprv)
}
}

pub trait HardenedNormalSplit {
fn hardened_normal_split(&self) -> (DerivationPath, Vec<u32>);
}
Expand Down
28 changes: 15 additions & 13 deletions src/view/pubkey_dlg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ use std::str::FromStr;
use lnpbp::bitcoin::util::bip32::{
self, ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey,
};
use lnpbp::bitcoin::util::{base58, key};
use lnpbp::bitcoin::util::key;
use lnpbp::{bitcoin, secp256k1};

use crate::model::{
DerivationComponents, DerivationRange, HardenedNormalSplit,
TrackingAccount, TrackingKey,
DerivationComponents, DerivationRange, FromSlip32, HardenedNormalSplit,
Slip32Error, TrackingAccount, TrackingKey,
};

static UI: &'static str = include_str!("../../ui/pubkey.glade");
Expand All @@ -38,7 +38,7 @@ pub enum Error {
#[from]
Secp(secp256k1::Error),

/// BIP32-specific error
/// Wrong public key data
#[display("{0}")]
#[from]
Key(key::Error),
Expand All @@ -48,10 +48,10 @@ pub enum Error {
#[from]
Bip32(bip32::Error),

/// Wrong extended public key data
/// SLIP-32 specific error
#[display("{0}")]
#[from]
Base58(base58::Error),
Slip32(Slip32Error),

/// Index range must not be empty
RangeNotSpecified,
Expand Down Expand Up @@ -500,12 +500,13 @@ impl PubkeyDlg {
let derivation = self.derivation_path(false)?;
let (branch_path, terminal_path) = derivation.hardened_normal_split();
let account_xpub =
ExtendedPubKey::from_str(&self.account_field.get_text());
let master_xpub = ExtendedPubKey::from_str(&self.xpub_field.get_text());
ExtendedPubKey::from_slip32_str(&self.account_field.get_text());
let master_xpub =
ExtendedPubKey::from_slip32_str(&self.xpub_field.get_text());
let index_ranges = self.derivation_ranges()?;

if let Ok(master_priv) =
ExtendedPrivKey::from_str(&self.xpub_field.get_text())
ExtendedPrivKey::from_slip32_str(&self.xpub_field.get_text())
{
let master_xpub =
ExtendedPubKey::from_private(&lnpbp::SECP256K1, &master_priv);
Expand Down Expand Up @@ -735,7 +736,7 @@ impl PubkeyDlg {
.collect::<DerivationPath>();

let (xpubkey, master) = if let Ok(master_priv) =
ExtendedPrivKey::from_str(&self.xpub_field.get_text())
ExtendedPrivKey::from_slip32_str(&self.xpub_field.get_text())
{
let master = ExtendedPubKey::from_private(
&lnpbp::SECP256K1,
Expand All @@ -749,8 +750,9 @@ impl PubkeyDlg {
master,
)
} else {
let master =
ExtendedPubKey::from_str(&self.xpub_field.get_text())?;
let master = ExtendedPubKey::from_slip32_str(
&self.xpub_field.get_text(),
)?;
let pk = master
.derive_pub(&lnpbp::SECP256K1, &derivation)
.map(|pk| {
Expand All @@ -762,7 +764,7 @@ impl PubkeyDlg {
if !self.account_field.get_text().is_empty() {
self.offset_chk.set_sensitive(false);
self.offset_chk.set_active(false);
let account = ExtendedPubKey::from_str(
let account = ExtendedPubKey::from_slip32_str(
&self.account_field.get_text(),
)?;
let pk = account.derive_pub(
Expand Down
Binary file added test/Electrum tests.bpro
Binary file not shown.

0 comments on commit 5a17598

Please sign in to comment.