From af61d7e7bc5630bdb5b607331e39db108fe0a51c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 10 Nov 2020 09:34:29 +0100 Subject: [PATCH] PSBT: adding global types (version, xpub) --- src/util/bip32.rs | 4 +-- src/util/psbt/map/global.rs | 67 +++++++++++++++++++++++++++++++++++-- src/util/psbt/mod.rs | 8 +++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/util/bip32.rs b/src/util/bip32.rs index 5611d78d..1dcabe42 100644 --- a/src/util/bip32.rs +++ b/src/util/bip32.rs @@ -390,7 +390,7 @@ pub enum Error { InvalidDerivationPathFormat, /// Unknown version magic bytes UnknownVersion([u8; 4]), - /// Encoded extended key data have wrong length + /// Encoded extended key data has wrong length WrongExtendedKeyLength(usize), /// Base58 encoding error Base58(base58::Error) @@ -406,7 +406,7 @@ impl fmt::Display for Error { 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::WrongExtendedKeyLength(ref len) => write!(f, "encoded extended key data has wrong length {}", len), Error::Base58(ref err) => write!(f, "base58 encoding error: {}", err), } } diff --git a/src/util/psbt/map/global.rs b/src/util/psbt/map/global.rs index ba6bf569..5a571e51 100644 --- a/src/util/psbt/map/global.rs +++ b/src/util/psbt/map/global.rs @@ -14,7 +14,7 @@ use std::collections::BTreeMap; use std::collections::btree_map::Entry; -use std::io::{self, Cursor}; +use std::io::{self, Cursor, Read}; use blockdata::transaction::Transaction; use consensus::{encode, Encodable, Decodable}; @@ -22,6 +22,7 @@ use util::psbt::map::Map; use util::psbt::raw; use util::psbt; use util::psbt::Error; +use util::bip32::{ExtendedPubKey, KeySource, Fingerprint, DerivationPath, ChildNumber}; /// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00 const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00; @@ -32,10 +33,15 @@ pub struct Global { /// The unsigned transaction, scriptSigs and witnesses for each input must be /// empty. pub unsigned_tx: Transaction, + /// The version number of this PSBT. If omitted, the version number is 0. + pub version: u32, + /// A global map from extended public keys to the used key fingerprint and + /// derivation path as defined by BIP 32 + pub xpub: BTreeMap, /// Unknown global key-value pairs. pub unknown: BTreeMap>, } -serde_struct_impl!(Global, unsigned_tx, unknown); +serde_struct_impl!(Global, unsigned_tx, version, xpub, unknown); impl Global { /// Create a Global from an unsigned transaction, error if not unsigned @@ -52,6 +58,8 @@ impl Global { Ok(Global { unsigned_tx: tx, + xpub: Default::default(), + version: 0, unknown: Default::default(), }) } @@ -124,7 +132,9 @@ impl Decodable for Global { fn consensus_decode(mut d: D) -> Result { let mut tx: Option = None; + let mut version: Option = None; let mut unknowns: BTreeMap> = Default::default(); + let mut xpub_map: BTreeMap = Default::default(); loop { match raw::Pair::consensus_decode(&mut d) { @@ -158,6 +168,57 @@ impl Decodable for Global { return Err(Error::InvalidKey(pair.key).into()) } } + // Global Xpub + 0x01 => { + if !pair.key.key.is_empty() { + let xpub = ExtendedPubKey::decode(&pair.key.key) + .map_err(|_| { + encode::Error::ParseFailed("Can't deserialize ExtendedPublicKey from global XPUB key data") + })?; + + if pair.value.len() % 4 != 0 { + return Err(encode::Error::ParseFailed("Incorrect length of global xpub list")) + } + + let keys_count = pair.value.len() / 4 - 1; + let mut decoder = Cursor::new(pair.value); + let mut fingerprint = [0u8; 4]; + decoder.read_exact(&mut fingerprint[..])?; + let mut path = Vec::::with_capacity(keys_count); + while let Ok(index) = u32::consensus_decode(&mut decoder) { + path.push(ChildNumber::from(index)) + } + let derivation = DerivationPath::from(path); + // Keys, according to BIP-174, must be unique + if xpub_map.insert(xpub, (Fingerprint::from(&fingerprint[..]), derivation)).is_some() { + return Err(encode::Error::ParseFailed("Repeated global xpub key")) + } + } else { + return Err(encode::Error::ParseFailed("Xpub global key must contain serialized Xpub data")) + } + } + // Version + 0xFB => { + // key has to be empty + if pair.key.key.is_empty() { + // there can only be one version + if version.is_none() { + let vlen: usize = pair.value.len(); + let mut decoder = Cursor::new(pair.value); + if vlen != 4 { + return Err(encode::Error::ParseFailed("Wrong global version value length (must be 4 bytes)")) + } + version = Some(Decodable::consensus_decode(&mut decoder)?); + if decoder.position() != vlen as u64 { + return Err(encode::Error::ParseFailed("data not consumed entirely when explicitly deserializing")) + } + } else { + return Err(Error::DuplicateKey(pair.key).into()) + } + } else { + return Err(Error::InvalidKey(pair.key).into()) + } + } _ => match unknowns.entry(pair.key) { Entry::Vacant(empty_key) => {empty_key.insert(pair.value);}, Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), @@ -171,6 +232,8 @@ impl Decodable for Global { if let Some(tx) = tx { let mut rv: Global = Global::from_unsigned_tx(tx)?; + rv.version = version.unwrap_or(0); + rv.xpub = xpub_map; rv.unknown = unknowns; Ok(rv) } else { diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 236643d7..f3384d4a 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -192,6 +192,8 @@ mod tests { input: vec![], output: vec![], }, + xpub: Default::default(), + version: 0, unknown: BTreeMap::new(), }, inputs: vec![], @@ -279,6 +281,8 @@ mod tests { }, ], }, + xpub: Default::default(), + version: 0, unknown: Default::default(), }; @@ -381,6 +385,8 @@ mod tests { }, ], }, + xpub: Default::default(), + version: 0, unknown: BTreeMap::new(), }, inputs: vec![Input { @@ -604,6 +610,8 @@ mod tests { }, ], }, + version: 0, + xpub: Default::default(), unknown: BTreeMap::new(), }, inputs: vec![Input {