Merge pull request #471 from LNP-BP/feat/psbt-keytypes

PSBT proprietary key system matching BIP 174
This commit is contained in:
Andrew Poelstra 2020-12-21 15:41:06 +00:00 committed by GitHub
commit 3c1117305f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 4 deletions

View File

@ -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<hashes::Error> for Error {
Error::HashParseError(e)
}
}
impl From<encode::Error> for Error {
fn from(err: encode::Error) -> Self {
match err {
encode::Error::Psbt(err) => err,
_ => Error::ConsensusEncoding,
}
}
}

View File

@ -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<ExtendedPubKey, KeySource>,
/// Global proprietary key-value pairs.
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown global key-value pairs.
pub unknown: BTreeMap<raw::Key, Vec<u8>>,
}
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::ProprietaryKey::from_key(raw_key.clone())?) {
Entry::Vacant(empty_key) => {empty_key.insert(raw_value);},
Entry::Occupied(_) => return Err(Error::DuplicateKey(raw_key).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.to_key(),
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<u32> = None;
let mut unknowns: BTreeMap<raw::Key, Vec<u8>> = Default::default();
let mut xpub_map: BTreeMap<ExtendedPubKey, (Fingerprint, DerivationPath)> = Default::default();
let mut proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>> = 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(raw::ProprietaryKey::from_key(pair.key.clone())?) {
Entry::Vacant(empty_key) => {empty_key.insert(pair.value);},
Entry::Occupied(_) => return Err(Error::DuplicateKey(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()),

View File

@ -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<hash160::Hash, Vec<u8>>,
/// HAS256 hash to preimage map
pub hash256_preimages: BTreeMap<sha256d::Hash, Vec<u8>>,
/// Proprietary key-value pairs for this input.
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown key-value pairs for this input.
pub unknown: BTreeMap<raw::Key, Vec<u8>>,
}
@ -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::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(_) => return Err(Error::DuplicateKey(raw_key).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 <PSBT_IN_HASH256, sha256d::Hash>|<Vec<u8>>)
}
for (key, value) in self.proprietary.iter() {
rv.push(raw::Pair {
key: key.to_key(),
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);

View File

@ -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<PublicKey, KeySource>,
/// Proprietary key-value pairs for this output.
pub proprietary: BTreeMap<raw::ProprietaryKey, Vec<u8>>,
/// Unknown key-value pairs for this output.
pub unknown: BTreeMap<raw::Key, Vec<u8>>,
}
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 <= <raw_key: PublicKey>|<raw_value: KeySource>
}
}
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(_) => return Err(Error::DuplicateKey(raw_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 <PSBT_OUT_BIP32_DERIVATION, PublicKey>|<KeySource>)
}
for (key, value) in self.proprietary.iter() {
rv.push(raw::Pair {
key: key.to_key(),
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);

View File

@ -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 {

View File

@ -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<Subtype = ProprietaryType> where Subtype: Copy + From<u8> + Into<u8> {
/// Proprietary type prefix used for grouping together keys under some
/// application and avoid namespace collision
pub prefix: Vec<u8>,
/// Custom proprietary subtype
pub subtype: Subtype,
/// Additional key bytes (like serialized public key data etc)
pub key: Vec<u8>,
}
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<Subtype> Encodable for ProprietaryKey<Subtype> where Subtype: Copy + From<u8> + Into<u8> {
fn consensus_encode<W: io::Write>(&self, mut e: W) -> Result<usize, encode::Error> {
let mut len = self.prefix.consensus_encode(&mut e)? + 1;
e.emit_u8(self.subtype.into())?;
len += e.write(&self.key)?;
Ok(len)
}
}
impl<Subtype> Decodable for ProprietaryKey<Subtype> where Subtype: Copy + From<u8> + Into<u8> {
fn consensus_decode<D: io::Read>(mut d: D) -> Result<Self, encode::Error> {
let prefix = Vec::<u8>::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<Subtype> ProprietaryKey<Subtype> where Subtype: Copy + From<u8> + Into<u8> {
/// Constructs [ProprietaryKey] from [Key]; returns
/// [Error::InvalidProprietaryKey] if `key` do not starts with 0xFC byte
pub fn from_key(key: Key) -> Result<Self, Error> {
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)
}
}
}