From c3024c3ebbec3cd34af5e8a70c1db2db0b1bbf24 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 5 Dec 2020 15:09:36 +0100 Subject: [PATCH 1/2] PSBT: basic support for proprietary keys --- src/util/psbt/map/global.rs | 24 +++++++++++++++++++++++- src/util/psbt/map/input.rs | 18 +++++++++++++++++- src/util/psbt/map/output.rs | 18 +++++++++++++++++- src/util/psbt/mod.rs | 4 ++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/util/psbt/map/global.rs b/src/util/psbt/map/global.rs index 15e9c945..e834d181 100644 --- a/src/util/psbt/map/global.rs +++ b/src/util/psbt/map/global.rs @@ -32,6 +32,8 @@ const PSBT_GLOBAL_UNSIGNED_TX: u8 = 0x00; const PSBT_GLOBAL_XPUB: u8 = 0x01; /// Type: Version Number PSBT_GLOBAL_VERSION = 0xFB const PSBT_GLOBAL_VERSION: u8 = 0xFB; +/// Type: Proprietary Use Type PSBT_GLOBAL_PROPRIETARY = 0xFC +const PSBT_GLOBAL_PROPRIETARY: u8 = 0xFC; /// A key-value map for global data. #[derive(Clone, Debug, PartialEq)] @@ -44,10 +46,12 @@ pub struct Global { /// A global map from extended public keys to the used key fingerprint and /// derivation path as defined by BIP 32 pub xpub: BTreeMap, + /// Global proprietary key-value pairs. + pub proprietary: BTreeMap>, /// Unknown global key-value pairs. pub unknown: BTreeMap>, } -serde_struct_impl!(Global, unsigned_tx, version, xpub, unknown); +serde_struct_impl!(Global, unsigned_tx, version, xpub, proprietary, unknown); impl Global { /// Create a Global from an unsigned transaction, error if not unsigned @@ -66,6 +70,7 @@ impl Global { unsigned_tx: tx, xpub: Default::default(), version: 0, + proprietary: Default::default(), unknown: Default::default(), }) } @@ -80,6 +85,10 @@ impl Map for Global { match raw_key.type_value { PSBT_GLOBAL_UNSIGNED_TX => return Err(Error::DuplicateKey(raw_key).into()), + PSBT_GLOBAL_PROPRIETARY => match self.proprietary.entry(raw_key) { + Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, + Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), + } _ => match self.unknown.entry(raw_key) { Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), @@ -135,6 +144,13 @@ impl Map for Global { }); } + for (key, value) in self.proprietary.iter() { + rv.push(raw::Pair { + key: key.clone(), + value: value.clone(), + }); + } + for (key, value) in self.unknown.iter() { rv.push(raw::Pair { key: key.clone(), @@ -201,6 +217,7 @@ impl Map for Global { } } + self.proprietary.extend(other.proprietary); self.unknown.extend(other.unknown); Ok(()) } @@ -215,6 +232,7 @@ impl Decodable for Global { let mut version: Option = None; let mut unknowns: BTreeMap> = Default::default(); let mut xpub_map: BTreeMap = Default::default(); + let mut proprietary: BTreeMap> = Default::default(); loop { match raw::Pair::consensus_decode(&mut d) { @@ -299,6 +317,10 @@ impl Decodable for Global { return Err(Error::InvalidKey(pair.key).into()) } } + PSBT_GLOBAL_PROPRIETARY => match proprietary.entry(pair.key) { + Entry::Vacant(empty_key) => {empty_key.insert(pair.value);}, + Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).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()), diff --git a/src/util/psbt/map/input.rs b/src/util/psbt/map/input.rs index ef8c3fc6..17f7503a 100644 --- a/src/util/psbt/map/input.rs +++ b/src/util/psbt/map/input.rs @@ -52,6 +52,8 @@ const PSBT_IN_SHA256: u8 = 0x0b; const PSBT_IN_HASH160: u8 = 0x0c; /// Type: HASH256 preimage PSBT_IN_HASH256 = 0x0d const PSBT_IN_HASH256: u8 = 0x0d; +/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC +const PSBT_IN_PROPRIETARY: u8 = 0xFC; /// A key-value map for an input of the corresponding index in the unsigned /// transaction. @@ -93,6 +95,8 @@ pub struct Input { pub hash160_preimages: BTreeMap>, /// HAS256 hash to preimage map pub hash256_preimages: BTreeMap>, + /// Proprietary key-value pairs for this input. + pub proprietary: BTreeMap>, /// Unknown key-value pairs for this input. pub unknown: BTreeMap>, } @@ -101,7 +105,7 @@ serde_struct_impl!( sighash_type, redeem_script, witness_script, bip32_derivation, final_script_sig, final_script_witness, ripemd160_preimages, sha256_preimages, hash160_preimages, hash256_preimages, - unknown + proprietary, unknown ); impl Map for Input { @@ -169,6 +173,10 @@ impl Map for Input { PSBT_IN_HASH256 => { psbt_insert_hash_pair(&mut self.hash256_preimages, raw_key, raw_value, error::PsbtHash::Hash256)?; } + PSBT_IN_PROPRIETARY => match self.proprietary.entry(raw_key) { + ::std::collections::btree_map::Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, + ::std::collections::btree_map::Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), + } _ => match self.unknown.entry(raw_key) { ::std::collections::btree_map::Entry::Vacant(empty_key) => { empty_key.insert(raw_value); @@ -237,6 +245,13 @@ impl Map for Input { rv.push(self.hash256_preimages as |>) } + for (key, value) in self.proprietary.iter() { + rv.push(raw::Pair { + key: key.clone(), + value: value.clone(), + }); + } + for (key, value) in self.unknown.iter() { rv.push(raw::Pair { key: key.clone(), @@ -261,6 +276,7 @@ impl Map for Input { self.sha256_preimages.extend(other.sha256_preimages); self.hash160_preimages.extend(other.hash160_preimages); self.hash256_preimages.extend(other.hash256_preimages); + self.proprietary.extend(other.proprietary); self.unknown.extend(other.unknown); merge!(redeem_script, self, other); diff --git a/src/util/psbt/map/output.rs b/src/util/psbt/map/output.rs index 7be5a37c..3e25d300 100644 --- a/src/util/psbt/map/output.rs +++ b/src/util/psbt/map/output.rs @@ -30,6 +30,8 @@ const PSBT_OUT_REDEEM_SCRIPT: u8 = 0x00; const PSBT_OUT_WITNESS_SCRIPT: u8 = 0x01; /// Type: BIP 32 Derivation Path PSBT_OUT_BIP32_DERIVATION = 0x02 const PSBT_OUT_BIP32_DERIVATION: u8 = 0x02; +/// Type: Proprietary Use Type PSBT_IN_PROPRIETARY = 0xFC +const PSBT_OUT_PROPRIETARY: u8 = 0xFC; /// A key-value map for an output of the corresponding index in the unsigned /// transaction. @@ -42,11 +44,13 @@ pub struct Output { /// A map from public keys needed to spend this output to their /// corresponding master key fingerprints and derivation paths. pub bip32_derivation: BTreeMap, + /// Proprietary key-value pairs for this output. + pub proprietary: BTreeMap>, /// Unknown key-value pairs for this output. pub unknown: BTreeMap>, } serde_struct_impl!( - Output, redeem_script, witness_script, bip32_derivation, unknown + Output, redeem_script, witness_script, bip32_derivation, proprietary, unknown ); impl Map for Output { @@ -72,6 +76,10 @@ impl Map for Output { self.bip32_derivation <= | } } + PSBT_OUT_PROPRIETARY => match self.proprietary.entry(raw_key) { + Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, + Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), + } _ => match self.unknown.entry(raw_key) { Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), @@ -96,6 +104,13 @@ impl Map for Output { rv.push(self.bip32_derivation as |) } + for (key, value) in self.proprietary.iter() { + rv.push(raw::Pair { + key: key.clone(), + value: value.clone(), + }); + } + for (key, value) in self.unknown.iter() { rv.push(raw::Pair { key: key.clone(), @@ -108,6 +123,7 @@ impl Map for Output { fn merge(&mut self, other: Self) -> Result<(), psbt::Error> { self.bip32_derivation.extend(other.bip32_derivation); + self.proprietary.extend(other.proprietary); self.unknown.extend(other.unknown); merge!(redeem_script, self, other); diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index f3384d4a..caaae382 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -194,6 +194,7 @@ mod tests { }, xpub: Default::default(), version: 0, + proprietary: BTreeMap::new(), unknown: BTreeMap::new(), }, inputs: vec![], @@ -283,6 +284,7 @@ mod tests { }, xpub: Default::default(), version: 0, + proprietary: Default::default(), unknown: Default::default(), }; @@ -387,6 +389,7 @@ mod tests { }, xpub: Default::default(), version: 0, + proprietary: BTreeMap::new(), unknown: BTreeMap::new(), }, inputs: vec![Input { @@ -612,6 +615,7 @@ mod tests { }, version: 0, xpub: Default::default(), + proprietary: Default::default(), unknown: BTreeMap::new(), }, inputs: vec![Input { From 7400bccb60a7d07c04e53d614814096e7ed203d6 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 5 Dec 2020 15:10:44 +0100 Subject: [PATCH 2/2] PSBT: proprietary keys type system according to BIP 174 --- src/util/psbt/error.rs | 16 ++++++++++ src/util/psbt/map/global.rs | 14 ++++----- src/util/psbt/map/input.rs | 8 ++--- src/util/psbt/map/output.rs | 8 ++--- src/util/psbt/raw.rs | 63 ++++++++++++++++++++++++++++++++++++- 5 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/util/psbt/error.rs b/src/util/psbt/error.rs index 9da6bd26..b00878fe 100644 --- a/src/util/psbt/error.rs +++ b/src/util/psbt/error.rs @@ -16,6 +16,7 @@ use std::error; use std::fmt; use blockdata::transaction::Transaction; +use consensus::encode; use util::psbt::raw; use hashes; @@ -38,6 +39,8 @@ pub enum Error { InvalidSeparator, /// Known keys must be according to spec. InvalidKey(raw::Key), + /// Non-proprietary key type found when proprietary key was expected + InvalidProprietaryKey, /// Keys within key-value map should never be duplicated. DuplicateKey(raw::Key), /// The scriptSigs for the unsigned transaction must be empty. @@ -71,12 +74,15 @@ pub enum Error { }, /// Data inconsistency/conflicting data during merge procedure MergeConflict(String), + /// Serialization error in bitcoin consensus-encoded structures + ConsensusEncoding, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::InvalidKey(ref rkey) => write!(f, "invalid key: {}", rkey), + Error::InvalidProprietaryKey => write!(f, "non-proprietary key type found when proprietary key was expected"), Error::DuplicateKey(ref rkey) => write!(f, "duplicate key: {}", rkey), Error::UnexpectedUnsignedTx { expected: ref e, actual: ref a } => write!(f, "different unsigned transaction: expected {}, actual {}", e.txid(), a.txid()), Error::NonStandardSigHashType(ref sht) => write!(f, "non-standard sighash type: {}", sht), @@ -94,6 +100,7 @@ impl fmt::Display for Error { write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash ) } Error::MergeConflict(ref s) => { write!(f, "Merge conflict: {}", s) } + Error::ConsensusEncoding => f.write_str("bitcoin consensus encoding error"), } } } @@ -106,3 +113,12 @@ impl From for Error { Error::HashParseError(e) } } + +impl From for Error { + fn from(err: encode::Error) -> Self { + match err { + encode::Error::Psbt(err) => err, + _ => Error::ConsensusEncoding, + } + } +} diff --git a/src/util/psbt/map/global.rs b/src/util/psbt/map/global.rs index e834d181..729fc91c 100644 --- a/src/util/psbt/map/global.rs +++ b/src/util/psbt/map/global.rs @@ -47,7 +47,7 @@ pub struct Global { /// derivation path as defined by BIP 32 pub xpub: BTreeMap, /// Global proprietary key-value pairs. - pub proprietary: BTreeMap>, + pub proprietary: BTreeMap>, /// Unknown global key-value pairs. pub unknown: BTreeMap>, } @@ -85,9 +85,9 @@ impl Map for Global { match raw_key.type_value { PSBT_GLOBAL_UNSIGNED_TX => return Err(Error::DuplicateKey(raw_key).into()), - PSBT_GLOBAL_PROPRIETARY => match self.proprietary.entry(raw_key) { + PSBT_GLOBAL_PROPRIETARY => match self.proprietary.entry(raw::ProprietaryKey::from_key(raw_key.clone())?) { Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, - Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), + Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key).into()), } _ => match self.unknown.entry(raw_key) { Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, @@ -146,7 +146,7 @@ impl Map for Global { for (key, value) in self.proprietary.iter() { rv.push(raw::Pair { - key: key.clone(), + key: key.to_key(), value: value.clone(), }); } @@ -232,7 +232,7 @@ impl Decodable for Global { let mut version: Option = None; let mut unknowns: BTreeMap> = Default::default(); let mut xpub_map: BTreeMap = Default::default(); - let mut proprietary: BTreeMap> = Default::default(); + let mut proprietary: BTreeMap> = Default::default(); loop { match raw::Pair::consensus_decode(&mut d) { @@ -317,9 +317,9 @@ impl Decodable for Global { return Err(Error::InvalidKey(pair.key).into()) } } - PSBT_GLOBAL_PROPRIETARY => match proprietary.entry(pair.key) { + PSBT_GLOBAL_PROPRIETARY => match proprietary.entry(raw::ProprietaryKey::from_key(pair.key.clone())?) { Entry::Vacant(empty_key) => {empty_key.insert(pair.value);}, - Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), + Entry::Occupied(_) => return Err(Error::DuplicateKey(pair.key).into()), } _ => match unknowns.entry(pair.key) { Entry::Vacant(empty_key) => {empty_key.insert(pair.value);}, diff --git a/src/util/psbt/map/input.rs b/src/util/psbt/map/input.rs index 17f7503a..b10641ed 100644 --- a/src/util/psbt/map/input.rs +++ b/src/util/psbt/map/input.rs @@ -96,7 +96,7 @@ pub struct Input { /// HAS256 hash to preimage map pub hash256_preimages: BTreeMap>, /// Proprietary key-value pairs for this input. - pub proprietary: BTreeMap>, + pub proprietary: BTreeMap>, /// Unknown key-value pairs for this input. pub unknown: BTreeMap>, } @@ -173,9 +173,9 @@ impl Map for Input { PSBT_IN_HASH256 => { psbt_insert_hash_pair(&mut self.hash256_preimages, raw_key, raw_value, error::PsbtHash::Hash256)?; } - PSBT_IN_PROPRIETARY => match self.proprietary.entry(raw_key) { + PSBT_IN_PROPRIETARY => match self.proprietary.entry(raw::ProprietaryKey::from_key(raw_key.clone())?) { ::std::collections::btree_map::Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, - ::std::collections::btree_map::Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), + ::std::collections::btree_map::Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key).into()), } _ => match self.unknown.entry(raw_key) { ::std::collections::btree_map::Entry::Vacant(empty_key) => { @@ -247,7 +247,7 @@ impl Map for Input { for (key, value) in self.proprietary.iter() { rv.push(raw::Pair { - key: key.clone(), + key: key.to_key(), value: value.clone(), }); } diff --git a/src/util/psbt/map/output.rs b/src/util/psbt/map/output.rs index 3e25d300..71e1d2ac 100644 --- a/src/util/psbt/map/output.rs +++ b/src/util/psbt/map/output.rs @@ -45,7 +45,7 @@ pub struct Output { /// corresponding master key fingerprints and derivation paths. pub bip32_derivation: BTreeMap, /// Proprietary key-value pairs for this output. - pub proprietary: BTreeMap>, + pub proprietary: BTreeMap>, /// Unknown key-value pairs for this output. pub unknown: BTreeMap>, } @@ -76,9 +76,9 @@ impl Map for Output { self.bip32_derivation <= | } } - PSBT_OUT_PROPRIETARY => match self.proprietary.entry(raw_key) { + PSBT_OUT_PROPRIETARY => match self.proprietary.entry(raw::ProprietaryKey::from_key(raw_key.clone())?) { Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, - Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), + Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key.clone()).into()), } _ => match self.unknown.entry(raw_key) { Entry::Vacant(empty_key) => {empty_key.insert(raw_value);}, @@ -106,7 +106,7 @@ impl Map for Output { for (key, value) in self.proprietary.iter() { rv.push(raw::Pair { - key: key.clone(), + key: key.to_key(), value: value.clone(), }); } diff --git a/src/util/psbt/raw.rs b/src/util/psbt/raw.rs index 16185715..0bd801fe 100644 --- a/src/util/psbt/raw.rs +++ b/src/util/psbt/raw.rs @@ -19,7 +19,7 @@ use std::{fmt, io}; -use consensus::encode::{self, Decodable, Encodable, VarInt, MAX_VEC_SIZE}; +use consensus::encode::{self, ReadExt, WriteExt, Decodable, Encodable, VarInt, serialize, deserialize, MAX_VEC_SIZE}; use hashes::hex::ToHex; use util::psbt::Error; @@ -43,6 +43,23 @@ pub struct Pair { } serde_struct_impl!(Pair, key, value); +/// Default implementation for proprietary key subtyping +pub type ProprietaryType = u8; + +/// Proprietary keys (i.e. keys starting with 0xFC byte) with their internal +/// structure according to BIP 174. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct ProprietaryKey where Subtype: Copy + From + Into { + /// Proprietary type prefix used for grouping together keys under some + /// application and avoid namespace collision + pub prefix: Vec, + /// Custom proprietary subtype + pub subtype: Subtype, + /// Additional key bytes (like serialized public key data etc) + pub key: Vec, +} +serde_struct_impl!(ProprietaryKey, prefix, subtype, key); + impl fmt::Display for Key { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( @@ -121,3 +138,47 @@ impl Decodable for Pair { }) } } + +impl Encodable for ProprietaryKey where Subtype: Copy + From + Into { + fn consensus_encode(&self, mut e: W) -> Result { + let mut len = self.prefix.consensus_encode(&mut e)? + 1; + e.emit_u8(self.subtype.into())?; + len += e.write(&self.key)?; + Ok(len) + } +} + +impl Decodable for ProprietaryKey where Subtype: Copy + From + Into { + fn consensus_decode(mut d: D) -> Result { + let prefix = Vec::::consensus_decode(&mut d)?; + let mut key = vec![]; + let subtype = Subtype::from(d.read_u8()?); + d.read_to_end(&mut key)?; + + Ok(ProprietaryKey { + prefix, + subtype, + key + }) + } +} + +impl ProprietaryKey where Subtype: Copy + From + Into { + /// Constructs [ProprietaryKey] from [Key]; returns + /// [Error::InvalidProprietaryKey] if `key` do not starts with 0xFC byte + pub fn from_key(key: Key) -> Result { + if key.type_value != 0xFC { + return Err(Error::InvalidProprietaryKey) + } + + Ok(deserialize(&key.key)?) + } + + /// Constructs full [Key] corresponding to this proprietary key type + pub fn to_key(&self) -> Key { + Key { + type_value: 0xFC, + key: serialize(self) + } + } +}