PSBT: adding global types (version, xpub)

This commit is contained in:
Dr Maxim Orlovsky 2020-11-10 09:34:29 +01:00
parent 6df16b7ce2
commit af61d7e7bc
No known key found for this signature in database
GPG Key ID: FFC0250947E5C6F7
3 changed files with 75 additions and 4 deletions

View File

@ -390,7 +390,7 @@ pub enum Error {
InvalidDerivationPathFormat, InvalidDerivationPathFormat,
/// Unknown version magic bytes /// Unknown version magic bytes
UnknownVersion([u8; 4]), UnknownVersion([u8; 4]),
/// Encoded extended key data have wrong length /// Encoded extended key data has wrong length
WrongExtendedKeyLength(usize), WrongExtendedKeyLength(usize),
/// Base58 encoding error /// Base58 encoding error
Base58(base58::Error) Base58(base58::Error)
@ -406,7 +406,7 @@ impl fmt::Display for Error {
Error::InvalidChildNumberFormat => f.write_str("invalid child number format"), Error::InvalidChildNumberFormat => f.write_str("invalid child number format"),
Error::InvalidDerivationPathFormat => f.write_str("invalid derivation path format"), Error::InvalidDerivationPathFormat => f.write_str("invalid derivation path format"),
Error::UnknownVersion(ref bytes) => write!(f, "unknown version magic bytes: {:?}", bytes), 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), Error::Base58(ref err) => write!(f, "base58 encoding error: {}", err),
} }
} }

View File

@ -14,7 +14,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::btree_map::Entry; use std::collections::btree_map::Entry;
use std::io::{self, Cursor}; use std::io::{self, Cursor, Read};
use blockdata::transaction::Transaction; use blockdata::transaction::Transaction;
use consensus::{encode, Encodable, Decodable}; use consensus::{encode, Encodable, Decodable};
@ -22,6 +22,7 @@ use util::psbt::map::Map;
use util::psbt::raw; use util::psbt::raw;
use util::psbt; use util::psbt;
use util::psbt::Error; use util::psbt::Error;
use util::bip32::{ExtendedPubKey, KeySource, Fingerprint, DerivationPath, ChildNumber};
/// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00 /// Type: Unsigned Transaction PSBT_GLOBAL_UNSIGNED_TX = 0x00
const PSBT_GLOBAL_UNSIGNED_TX: u8 = 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 /// The unsigned transaction, scriptSigs and witnesses for each input must be
/// empty. /// empty.
pub unsigned_tx: Transaction, 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<ExtendedPubKey, KeySource>,
/// Unknown global key-value pairs. /// Unknown global key-value pairs.
pub unknown: BTreeMap<raw::Key, Vec<u8>>, pub unknown: BTreeMap<raw::Key, Vec<u8>>,
} }
serde_struct_impl!(Global, unsigned_tx, unknown); serde_struct_impl!(Global, unsigned_tx, version, xpub, unknown);
impl Global { impl Global {
/// Create a Global from an unsigned transaction, error if not unsigned /// Create a Global from an unsigned transaction, error if not unsigned
@ -52,6 +58,8 @@ impl Global {
Ok(Global { Ok(Global {
unsigned_tx: tx, unsigned_tx: tx,
xpub: Default::default(),
version: 0,
unknown: Default::default(), unknown: Default::default(),
}) })
} }
@ -124,7 +132,9 @@ impl Decodable for Global {
fn consensus_decode<D: io::Read>(mut d: D) -> Result<Self, encode::Error> { fn consensus_decode<D: io::Read>(mut d: D) -> Result<Self, encode::Error> {
let mut tx: Option<Transaction> = None; let mut tx: Option<Transaction> = None;
let mut version: Option<u32> = None;
let mut unknowns: BTreeMap<raw::Key, Vec<u8>> = Default::default(); let mut unknowns: BTreeMap<raw::Key, Vec<u8>> = Default::default();
let mut xpub_map: BTreeMap<ExtendedPubKey, (Fingerprint, DerivationPath)> = Default::default();
loop { loop {
match raw::Pair::consensus_decode(&mut d) { match raw::Pair::consensus_decode(&mut d) {
@ -158,6 +168,57 @@ impl Decodable for Global {
return Err(Error::InvalidKey(pair.key).into()) 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::<ChildNumber>::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) { _ => match unknowns.entry(pair.key) {
Entry::Vacant(empty_key) => {empty_key.insert(pair.value);}, Entry::Vacant(empty_key) => {empty_key.insert(pair.value);},
Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()), Entry::Occupied(k) => return Err(Error::DuplicateKey(k.key().clone()).into()),
@ -171,6 +232,8 @@ impl Decodable for Global {
if let Some(tx) = tx { if let Some(tx) = tx {
let mut rv: Global = Global::from_unsigned_tx(tx)?; let mut rv: Global = Global::from_unsigned_tx(tx)?;
rv.version = version.unwrap_or(0);
rv.xpub = xpub_map;
rv.unknown = unknowns; rv.unknown = unknowns;
Ok(rv) Ok(rv)
} else { } else {

View File

@ -192,6 +192,8 @@ mod tests {
input: vec![], input: vec![],
output: vec![], output: vec![],
}, },
xpub: Default::default(),
version: 0,
unknown: BTreeMap::new(), unknown: BTreeMap::new(),
}, },
inputs: vec![], inputs: vec![],
@ -279,6 +281,8 @@ mod tests {
}, },
], ],
}, },
xpub: Default::default(),
version: 0,
unknown: Default::default(), unknown: Default::default(),
}; };
@ -381,6 +385,8 @@ mod tests {
}, },
], ],
}, },
xpub: Default::default(),
version: 0,
unknown: BTreeMap::new(), unknown: BTreeMap::new(),
}, },
inputs: vec![Input { inputs: vec![Input {
@ -604,6 +610,8 @@ mod tests {
}, },
], ],
}, },
version: 0,
xpub: Default::default(),
unknown: BTreeMap::new(), unknown: BTreeMap::new(),
}, },
inputs: vec![Input { inputs: vec![Input {