diff --git a/src/util/bip32.rs b/src/util/bip32.rs index 5611d78d..964e79e1 100644 --- a/src/util/bip32.rs +++ b/src/util/bip32.rs @@ -126,14 +126,14 @@ impl ChildNumber { /// Returns `true` if the child number is a [`Normal`] value. /// /// [`Normal`]: #variant.Normal - pub fn is_normal(self) -> bool { + pub fn is_normal(&self) -> bool { !self.is_hardened() } /// Returns `true` if the child number is a [`Hardened`] value. /// /// [`Hardened`]: #variant.Hardened - pub fn is_hardened(self) -> bool { + pub fn is_hardened(&self) -> bool { match self { ChildNumber::Hardened {..} => true, ChildNumber::Normal {..} => false, @@ -299,6 +299,11 @@ impl<'a> Iterator for DerivationPathIterator<'a> { } impl DerivationPath { + /// Returns length of the derivation path + pub fn len(&self) -> usize { + self.0.len() + } + /// Create a new [DerivationPath] that is a child of this one. pub fn child(&self, cn: ChildNumber) -> DerivationPath { let mut path = self.0.clone(); @@ -390,7 +395,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 +411,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/error.rs b/src/util/psbt/error.rs index f8bf549a..9da6bd26 100644 --- a/src/util/psbt/error.rs +++ b/src/util/psbt/error.rs @@ -20,7 +20,7 @@ use util::psbt::raw; use hashes; -#[derive(Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] /// Enum for marking psbt hash error pub enum PsbtHash { Ripemd, @@ -29,7 +29,7 @@ pub enum PsbtHash { Hash256, } /// Ways that a Partially Signed Transaction might fail. -#[derive(Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum Error { /// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most /// significant byte order. @@ -68,7 +68,9 @@ pub enum Error { preimage: Vec, /// Hash value hash: Vec, - } + }, + /// Data inconsistency/conflicting data during merge procedure + MergeConflict(String), } impl fmt::Display for Error { @@ -91,6 +93,7 @@ impl fmt::Display for Error { // directly using debug forms of psbthash enums write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash ) } + Error::MergeConflict(ref s) => { write!(f, "Merge conflict: {}", s) } } } } diff --git a/src/util/psbt/map/global.rs b/src/util/psbt/map/global.rs index ba6bf569..15e9c945 100644 --- a/src/util/psbt/map/global.rs +++ b/src/util/psbt/map/global.rs @@ -14,7 +14,8 @@ use std::collections::BTreeMap; use std::collections::btree_map::Entry; -use std::io::{self, Cursor}; +use std::io::{self, Cursor, Read}; +use std::cmp; use blockdata::transaction::Transaction; use consensus::{encode, Encodable, Decodable}; @@ -22,9 +23,15 @@ use util::psbt::map::Map; use util::psbt::raw; use util::psbt; use util::psbt::Error; +use util::endian::u32_to_array_le; +use util::bip32::{ExtendedPubKey, KeySource, Fingerprint, DerivationPath, ChildNumber}; /// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00 const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00; +/// Type: Extended Public Key PSBT_GLOBAL_XPUB = 0x01 +const PSBT_GLOBAL_XPUB: u8 = 0x01; +/// Type: Version Number PSBT_GLOBAL_VERSION = 0xFB +const PSBT_GLOBAL_VERSION: u8 = 0xFB; /// A key-value map for global data. #[derive(Clone, Debug, PartialEq)] @@ -32,10 +39,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 +64,8 @@ impl Global { Ok(Global { unsigned_tx: tx, + xpub: Default::default(), + version: 0, unknown: Default::default(), }) } @@ -95,6 +109,32 @@ impl Map for Global { }, }); + for (xpub, (fingerprint, derivation)) in &self.xpub { + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_XPUB, + key: xpub.encode().to_vec(), + }, + value: { + let mut ret = Vec::with_capacity(4 + derivation.len() * 4); + ret.extend(fingerprint.as_bytes()); + derivation.into_iter().for_each(|n| ret.extend(&u32_to_array_le((*n).into()))); + ret + } + }); + } + + // Serializing version only for non-default value; otherwise test vectors fail + if self.version > 0 { + rv.push(raw::Pair { + key: raw::Key { + type_value: PSBT_GLOBAL_VERSION, + key: vec![], + }, + value: u32_to_array_le(self.version).to_vec() + }); + } + for (key, value) in self.unknown.iter() { rv.push(raw::Pair { key: key.clone(), @@ -105,6 +145,8 @@ impl Map for Global { Ok(rv) } + // Keep in mind that according to BIP 174 this function must be commutative, i.e. + // A.merge(B) == B.merge(A) fn merge(&mut self, other: Self) -> Result<(), psbt::Error> { if self.unsigned_tx != other.unsigned_tx { return Err(psbt::Error::UnexpectedUnsignedTx { @@ -113,6 +155,52 @@ impl Map for Global { }); } + // BIP 174: The Combiner must remove any duplicate key-value pairs, in accordance with + // the specification. It can pick arbitrarily when conflicts occur. + + // Keeping the highest version + self.version = cmp::max(self.version, other.version); + + // Merging xpubs + for (xpub, (fingerprint1, derivation1)) in other.xpub { + match self.xpub.entry(xpub) { + Entry::Vacant(entry) => { + entry.insert((fingerprint1, derivation1)); + }, + Entry::Occupied(mut entry) => { + // Here in case of the conflict we select the version with algorithm: + // 1) if everything is equal we do nothing + // 2) report an error if + // - derivation paths are equal and fingerprints are not + // - derivation paths are of the same length, but not equal + // - derivation paths has different length, but the shorter one + // is not the strict suffix of the longer one + // 3) choose longest derivation otherwise + + let (fingerprint2, derivation2) = entry.get().clone(); + + if derivation1 == derivation2 && fingerprint1 == fingerprint2 + { + continue + } + else if + derivation1.len() < derivation2.len() && + derivation1[..] == derivation2[derivation2.len() - derivation1.len()..] + { + continue + } + else if derivation2[..] == derivation1[derivation1.len() - derivation2.len()..] + { + entry.insert((fingerprint1, derivation1)); + continue + } + return Err(psbt::Error::MergeConflict(format!( + "global xpub {} has inconsistent key sources", xpub + ).to_owned())); + } + } + } + self.unknown.extend(other.unknown); Ok(()) } @@ -124,7 +212,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 +248,57 @@ impl Decodable for Global { return Err(Error::InvalidKey(pair.key).into()) } } + PSBT_GLOBAL_XPUB => { + 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.is_empty() || pair.value.len() % 4 != 0 { + return Err(encode::Error::ParseFailed("Incorrect length of global xpub derivation data")) + } + + let child_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(child_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")) + } + } + PSBT_GLOBAL_VERSION => { + // 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)?); + // We only understand version 0 PSBTs. According to BIP-174 we + // should throw an error if we see anything other than version 0. + if version != Some(0) { + return Err(encode::Error::ParseFailed("PSBT versions greater than 0 are not supported")) + } + } 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 +312,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 {