PSBT: adding global types (version, xpub)
This commit is contained in:
parent
6df16b7ce2
commit
af61d7e7bc
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue