Skip to content

New PSBT global keys#499

Merged
apoelstra merged 8 commits intorust-bitcoin:masterfrom
BP-WG:pending/psbt-global
Dec 20, 2020
Merged

New PSBT global keys#499
apoelstra merged 8 commits intorust-bitcoin:masterfrom
BP-WG:pending/psbt-global

Conversation

@dr-orlovsky
Copy link
Copy Markdown
Collaborator

Based/depends on #470

Part of epic #473

@apoelstra apoelstra added the API break This PR requires a version bump for the next release label Oct 14, 2020
Copy link
Copy Markdown
Collaborator

@stevenroose stevenroose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You also forgot to implement Map::get_pairs to do the serialization part.

Some unit tests that add some roundtrips of some PSBTs with the new keys would also be appreciated.

@apoelstra
Copy link
Copy Markdown
Member

needs rebase

@dr-orlovsky
Copy link
Copy Markdown
Collaborator Author

Rebased and addressed @stevenroose comments. NB: this is based on #470, which also had review comments (they are addressed now, but #470 needs to be merged first)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: has wrong length

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BIP only specifies versions for Testnet, is it safe to use the same for Signet and Regtest

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's reasonable for now. Regtest copies a lot of values from testnet.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the from_slice() only fail if size is incorrect?
It looks like https://github.com/bitcoin-core/secp256k1/blob/9e5939d2848df5508e3a6eb038273b28d255248c/src/secp256k1.c#L550-L559 can fail if there is overflow or if secret key is zero.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the other branch and now fine after the rebase

@apoelstra
Copy link
Copy Markdown
Member

Can you squash these commits together? It's not too long to read, and it's a bit harder to review because you make changes that are later modified.

This comment was marked as resolved.

This comment was marked as resolved.

@justinmoon
Copy link
Copy Markdown

justinmoon commented Nov 25, 2020

I think this breaks PSBT serialization.

The impl_psbtmap_consensus_encoding macro calls Global::get_pairs which needs to be updated after you removed version and xpub values from Global::unknown and put them in their own fields.

Here's a failing test.

Otherwise it looks great!

@dr-orlovsky
Copy link
Copy Markdown
Collaborator Author

@justinmoon you are right, and even more: we need to do a proper merge according to BIP174 rules and resolve conflicts. Working on the change

@dr-orlovsky
Copy link
Copy Markdown
Collaborator Author

@apoelstra squashed old commits; new commits are on top and they do not change previous code but splits added functionality into logical blocks for review simplification

In general, it was not that trivial to do the merge procedure and key pair export; there were two questions which I do not know the right answer for, but proposing what I think will work the best:

  1. What to do when two PSBTs global xpub match by value, but not derivation/key source? Spec says that it's up to an implementation to decide, so we have to decide :)
  2. Do we need to serialize default 0 version, or just ignore this case? If we will serialize, this will break test vectors; if not, it may break roundtrips if the original PSBT contained explicit 0-version global key

My current implementation:

  1. Uses special multi-step algorithm described here: 8b672c3#diff-2581ef26b841ed4069017f0e46e1393216bf6b413302ae34f248105526c8755bR137-R164
  2. Does not serialize default 0 version as a separate key.

@apoelstra
Copy link
Copy Markdown
Member

  1. I'm happy with what you've done. I would've gone even simpler and just taken the lexicograpically first one :) but you're right that probably "longest first" is likely to do the right thing in realistic conflict scenarios.
  2. Agreed, we'll just break round-tripping (and will have to add extra canonicalization to our fuzztests)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause runtime error. ret is a vec with length 0, so you can't use vec[0..4]

Copy link
Copy Markdown
Collaborator Author

@dr-orlovsky dr-orlovsky Nov 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point, thank you. Fixed

@stevenroose
Copy link
Copy Markdown
Collaborator

You seem to have some Rust v1.29 compilation error in the ordering algorithm.

@dr-orlovsky
Copy link
Copy Markdown
Collaborator Author

@stevenroose @apoelstra 1.29 fixed for all commit history. It's extremely stupid with borrow checks and making it compiling required an unnecessary clone; I have tried everything else and it simply was not working with 1.29 :(

@dr-orlovsky dr-orlovsky requested a review from apoelstra December 7, 2020 15:42
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the correct term is "commutative"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@dr-orlovsky dr-orlovsky requested a review from apoelstra December 7, 2020 19:04
Copy link
Copy Markdown
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack 794c5196a0eb4ec635a35cf09708ff8da6a00d28

apoelstra
apoelstra previously approved these changes Dec 7, 2020
Copy link
Copy Markdown
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack 794c5196a0eb4ec635a35cf09708ff8da6a00d28

stevenroose
stevenroose previously approved these changes Dec 15, 2020
Copy link
Copy Markdown
Collaborator

@stevenroose stevenroose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK d1ace733e0de532707df7140ee5be1eb3faedbe0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Largest here means just by ordering the bytes, right? So 0xdeadbeef wins from 0xbeadbeef? This is a bit arbitrary. But I suppose I can agree that it's better to do something then to error.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. Actually since fingerprint is [u8; 4] and not u32 I assume it uses little-endian comparison. And yes, this is arbitrary

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd perhaps rephrase "choose largest pingerprint using binary ordering" because this suggests that the fingerprint "size" might have real meaning :p

@stevenroose
Copy link
Copy Markdown
Collaborator

@apoelstra you "approved" a different commit than the hash you mention inside the approve message. Can you confirm you meant to ack d1ace73?

Copy link
Copy Markdown
Collaborator

@stevenroose stevenroose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a bunch of nits. I didn't want to make them to not hold up the MR, but since your CI fails and you need to push changes anyway, I thought I'd leave them anyway.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: There's no need to do this. It's probably microscopically more efficient to leave it self.

Copy link
Copy Markdown
Collaborator Author

@dr-orlovsky dr-orlovsky Dec 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not about efficiency, this is required by compiler, otherwise we get

error[E0631]: type mismatch in function arguments
   --> src/util/psbt/map/global.rs:182:71
    |
182 |                         (fp, deriv.len(), deriv.into_iter().rposition(ChildNumber::is_normal))
    |                                                                       ^^^^^^^^^^^^^^^^^^^^^^ expected signature of `fn(&ChildNumber) -> _`
    | 
   ::: src/util/bip32.rs:129:5
    |
129 |     pub fn is_normal(self) -> bool {
    |     ------------------------------ found signature of `fn(ChildNumber) -> _`

basically you need &self in order to be able to use the functions inside the iterators

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just add .cloned() on the iterator before .rposition. Or .copied() in Rust 1.36. But what you did here is fine.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: There's no need to do this. It's probably microscopically more efficient to leave it self.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be for n in &derivation without the index. Or even

derivation.for_each(|n| ret.extend(&u32_to_array_le(n.into())));

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it can't be: there is no iter() for Derivation. You can only use derivation.into_iter()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think cmp::max(self.version, other.version) reads better; but very much a nit :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd perhaps rephrase "choose largest pingerprint using binary ordering" because this suggests that the fingerprint "size" might have real meaning :p

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Put on same line as string as long as it doesn't exceed 100 width :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This exceeds 100 characters, right? how about putting the encode::Error::ParseFailed on the same line as .map_err?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think you can actually do

let mut fingerprint = Fingerprint::default();
decoder.read_exact(&mut fingerprint[..])?;

but I'm not 100% sure

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't: there is no IndexMut impl for Fingerprint

Plus necessary changes to BIP 32 implementations and error type
Conflicts:
	src/util/psbt/map/global.rs
@dr-orlovsky dr-orlovsky dismissed stale reviews from stevenroose and apoelstra via 8b16662 December 15, 2020 15:04
@dr-orlovsky
Copy link
Copy Markdown
Collaborator Author

Had to rebase since it had conflicts with merged #530 and had a failed fuzz tests. Also used this opportunity to address @stevenroose nits. Will need a re-ACK from you guys, @apoelstra, @stevenroose

Copy link
Copy Markdown
Contributor

@sgeisler sgeisler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think by and large this PR is in a good state. I'm still not super happy with the merge logic, it seems way too convoluted, reasoning about it was somewhat hard and I'm not sure it's entirely consistent. But since many reviewed so far and didn't take issue with it I'd be fine with it since the BIP didn't specify to detect such inconsistencies afaik. @apoelstra, what do you think, keep polishing or merge?

Copy link
Copy Markdown
Contributor

@sgeisler sgeisler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found some logic bugs which is probably a good indicator to add some tests for the newly added logic.

Copy link
Copy Markdown
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack 7f5c279

@apoelstra apoelstra merged commit a6264cf into rust-bitcoin:master Dec 20, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API break This PR requires a version bump for the next release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants