BIP174 (de)serialization support#103
Conversation
|
After a few days of grinding away, I have an implementation with tests here Too tired right now to write out the entire documentation but here are some comments:
Any initial comments are welcome as I attempt to finalize this |
|
With all the new serialization/deserialization code this almost certainly needs new entries in fuzz/fuzz_targets. |
320948e to
d64fcec
Compare
|
@TheBlueMatt Works now, tests passed, fuzz tests are there. |
|
@TheBlueMatt @apoelstra Cleaned up my commits... First 3 commits which are non-PSBT are also available as cherry-picks at #122 #123 #124 |
|
Made PSBT-specific deserialization not influence other modules |
jeandudey
left a comment
There was a problem hiding this comment.
Greate work! I left my two cents 👍
src/util/mod.rs
Outdated
There was a problem hiding this comment.
Shouldn't this variant be named Psbt? All types with acronyms in the codebase only have the first letter uppercased e.g. Sha256dHash (C-CASE).
There was a problem hiding this comment.
See 7a6df92 and 5168c7c, will rebase
There was a problem hiding this comment.
I think that guideline applies to all types with the PSBT prefix (PSBTGlobal and such ones).
src/util/mod.rs
Outdated
There was a problem hiding this comment.
Please add a #[doc(hidden)] attribute (C-HIDDEN).
There was a problem hiding this comment.
Should that be added to all the From<*> for Error impls?
There was a problem hiding this comment.
@jeandudey let's open up another PR after this gets merged to fix all of them?
There was a problem hiding this comment.
Yes, it's up for another PR.
There was a problem hiding this comment.
Going to wait until this one's merged to do this for all Froms at once
There was a problem hiding this comment.
Maybe we should change SimpleDecoder to always use util::Error rather than being parameterized, and add a pub use util::Error at the top-level, in the same PR.
There was a problem hiding this comment.
#127 added #[doc(hidden)] everywhere - we should do so in this PR then as well.
src/util/psbt/psbtmap/psbtglobal.rs
Outdated
There was a problem hiding this comment.
It seems weird to implement this to a std type, isn't it more clear to have a from_transaction method in PSBTGlobal?
Something along the lines:
impl PSBTGlobal {
pub fn from_transaction(tx: Transaction) -> Result<PSBTGlobal, util::Error> {
....
}
}There was a problem hiding this comment.
How about I add this and leave the From version as well?
There was a problem hiding this comment.
Done for both PsbtGlobal and PartiallySignedTransaction
9b17114 to
866c2b5
Compare
|
Needs rebasing on the new |
src/util/psbt/mod.rs
Outdated
There was a problem hiding this comment.
Is it a standard Rust idiom to impl From on a Result? I find it very confusing, From is supposed to be for things that are conceptually trivial conversions or reinterpretations. But a Transaction is nothing at all like a Result<T, E>.
I would prefer that PartiallySignedTransaction had a from_transaction method on it.
There was a problem hiding this comment.
I find it weird too, seems confunsing
There was a problem hiding this comment.
Agreed, I was trying to emulate the new TryFrom trait that isn't in 1.14.0, but will just use the dedicated method.
src/util/psbt/mod.rs
Outdated
There was a problem hiding this comment.
nit: you can use *b"psbt" rather than an explicit array here
There was a problem hiding this comment.
Awesome! Much cleaner...
src/util/psbt/mod.rs
Outdated
There was a problem hiding this comment.
nit: b"psbt".consensus_encode(s)?
src/util/psbt/mod.rs
Outdated
There was a problem hiding this comment.
I think this is wrong - search " The transaction format is specified as follows:" in BIP 174 which says that there must be a map for every input and output. Using with_capacity() will create empty vectors, so that serializing a fresh PartiallySignedTransaction will result in an invalid PSBT.
I think providing empty maps with vec![Default::default(); tx.input.len()] should be fine; then an Updater can add actual info.
|
I'd like if none of the types had |
src/util/psbt/psbtmap/psbtglobal.rs
Outdated
There was a problem hiding this comment.
You need to check at some point that raw_kv_pair.key.key.is_empty(), because as written this will deserialize any key with the type_value 0x00, no matter its length or contents, which the BIP forbids.
Also, Transaction::deserialize will ignore trailing bytes, which you should flag an error on (and also flag an error if it's signed, has a witness array, etc). Annoyingly to catch trailing bytes you need to copy the RawDecoder logic out of network::serialize::deserialize, unwrap the rawdecoder when its done, and check that the underlying cursor is spent.
There was a problem hiding this comment.
Hmmm... Anything else that will ignore trailing bytes? I'd be happy to check for all of them
There was a problem hiding this comment.
Yes, the deserializer will ignore trailing bytes on all Bitcoin objects because they all self-describe their sizes, and the deserializer is meant to work with streams where "no more bytes" is not always well-defined.
There was a problem hiding this comment.
Hmmm... I think that makes sense for ConsensusDecodable, but perhaps deserialize should error if the cursor is not consumed as the caller is explicitly saying that the bytes provided should deserialize to a certain type?
da0bf67 to
da36b34
Compare
|
After long IRC back-and-forth, it seems we need to have a separate Transaction decoder for PSBT - because we want to support 0-input-decoding in non-witness-serialization-format in PSBT, but probably cant do it in non-PSBT (to fix the SegWit-ambiguity-bug, the easiest thing to do is to encode 0-input txn with witnesses always). |
|
@TheBlueMatt Good thing we have PSBT specific serialization/deserialization traits... Will do. |
src/util/psbt/map/global.rs
Outdated
There was a problem hiding this comment.
This needs to be replaced with manual serialization code to ensure 0-input transactions are serialized without witnesses. (And the corresponding unit test needs to be changed.)
+ value: {
+ let mut ret = vec![];
+ self.unsigned_tx.version.consensus_encode(&mut ret).unwrap();
+ self.unsigned_tx.input.consensus_encode(&mut ret).unwrap();
+ self.unsigned_tx.output.consensus_encode(&mut ret).unwrap();
+ self.unsigned_tx.lock_time.consensus_encode(&mut ret).unwrap();
+ ret
+ },
});
works.
There was a problem hiding this comment.
Same for the non_witneess_utxo in Input? Would it make sense if I just made sure that psbt::Serialize for Transaction always manually serializes?
There was a problem hiding this comment.
Hmm, non_witness_utxo doesn't necessarily mean that the previous tx doesn't have witnesses, right? Not sure if they are entirely irrelevant. They are for txid generation, which I think this field is primarily used for, but I wouldn't dare claim they are entirely unused.
There was a problem hiding this comment.
@dongcarl the test vectors have witnesses in non_witness_utxo. You need to support segwit witnesses. (And this is fine, everything is unambiguous since the referenced transactions need to be valid, i.e. have a nonzero number of outputs).
|
Couple comments which don't really match any lines of code:
|
|
Key types are replaced and manual serialization done. |
stevenroose
left a comment
There was a problem hiding this comment.
Looks good! Just the nit about adding an explanatory comment.
src/util/psbt/map/global.rs
Outdated
There was a problem hiding this comment.
Only nit: Add a comment explaining why you're doing the manual serialization here. New readers will be confused otherwise.
- Implement psbt::Error data type - Implement conversion from psbt::Error to util::Error - Create util::psbt module - Create non-public util::psbt::error module
- Add (en)decoding logic for said data types
- Implement psbt::Map trait for psbt::Global - Add converting constructor logic from Transaction for psbt::Global - Add (en)decoding logic for psbt::Global - Always deserialize unsigned_tx as non-witness - Add trait for PSBT (de)serialization - Implement PSBT (de)serialization trait for relevant psbt::Global types - Add macros for consensus::encode-backed PSBT (de)serialization implementations - Add macro for implementing encoding logic for PSBT key-value maps
- Implement psbt::Map trait for psbt::Output - Add (en)decoding logic for psbt::Output - Implement PSBT (de)serialization trait for relevant psbt::Output types - Add macro for merging fields for PSBT key-value maps - Add macro for implementing decoding logic for PSBT key-value maps - Add convenience macro for implementing both encoding and decoding logic for PSBT key-value maps - Add macro for inserting raw PSBT key-value pairs into PSBT key-value maps - Add macro for getting raw PSBT key-value pairs from PSBT key-value maps
|
I think that the following changes can be done in separate PRs, and I currently don't have the bandwidth to complete them:
Let's get minimal functionality merged in, then we can take our time expanding on it. |
- Implement psbt::Map trait for psbt::Input - Add (en)decoding logic for psbt::Input - Implement PSBT (de)serialization trait for relevant psbt::Input types
- Add merging logic for PartiallySignedTransactions - Add (en)decoding logic for PartiallySignedTransaction - Add converting constructor logic from Transaction for PartiallySignedTransaction - Add extracting constructor logic from PartiallySignedTransaction for Transaction Squashed in fixes from stevenroose <stevenroose@gmail.com> - Prevent PSBT::extract_tx from panicking - Make PartiallySignedTransaction fields public
- Add macro for decoding and unwrapping PartiallySignedTransaction from hex string
|
Sounds good. I can do fuzzing. Probably it is blocked on deciding how to parse hybrid keys (currently we interpret them as uncompressed and therefore reserialize them wrong; maybe we should reject them, but they are permissible on the blockchain..) |
Fix minimum_sum_amount field name
* work * key wallet tests * key wallet manager * fixes * fixes * fixes * more work * refactoring * fixes
This is currently a non-working work-in-progress to add BIP174 (de)serialization support to rust-bitcoin.
I'm wondering about the conventions of this project so I can conform to them better, namely:
KeyIDtoutil::bip32::ExtendedPubKey)psbt.rsbelong insrc/networkor some other module?First time contributing... Excited 😄