New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add PublicKey::to_sort_key method for use with sorting by key #1084
Conversation
I added a vector with mixed uncompressed and compressed. Other vectors are welcome. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks usable. Though I have some more ideas, I'm not opposed to merging, except I'd really like to not have the get_
prefix.
It'd be nice to have some benchmarks too. I suspect for small slices (2-3 elements or so) sort_by_cached_key
is slower than sort_unstable_by_key
and if so it'd be handy to document it.
New fixes:
|
Hmm,
I'm starting to like the latter because it serves another purpose too, so it's more useful but the former may be very slightly faster when sorting (skips computing length; though length computing is very fast) |
Removed Option. Wrapped tuple in opaque struct that derives |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm open to accepting this (modulo doc) but would like to see more discussion from others, especially regarding idea of changing to_bytes
.
if self.compressed { | ||
let bytes = self.inner.serialize(); | ||
let mut res = [0; 32]; | ||
res[..].copy_from_slice(&bytes[1..33]); | ||
(bytes[0], res, None) | ||
SortKey(bytes[0], res, [0; 32]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For #1060 we could use slice patterns here: let [prefix, x @ ..] = self.inner.serialize(); SortKey(prefix, x, [0; u32])
I improved the doc example and squashed everything into 1 commit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 24f0441
I'm a little tempted to bikeshed on the On the other hand I really doubt the speedup is measurable, so without benchmarks I won't push it. I'm just mentioning it for the record. |
re: Option: I don't care either way. I ran some simple benchmarks locally (on rust playground) and it seems like an extra 32 0 == 0 u8 comparisons are about 0.8 ns. However, like I said in the discussion, if you think about the most common use cases for this sorting, having two or more compressed pubkeys that are equal (which is the condition for hitting the 0 comparisons) is always a sign of something going wrong. I can't think of one use case for sorting PublicKey objects where having two identical compressed keys isn't an indication of a serious bug somewhere else, and most correct programs will therefore never even hit the condition for the extra compares. I would be interested if someone can think of a use case where two equal pubkeys isn't a sign of another issue... But like you said, I think we can just merge as-is. Just wanted to share my self-justification. |
@apoelstra the good thing about the newtype is we can change our minds without breaking the API. :) We could also use zeroth byte to decide whether to compare whole array or not, it can even be branch-free. @junderw block explorers counting address reuse within blocks. Not sure if useful but at least shouldn't be obviously wrong. |
Also as an added note for reference on how Bitcoin Core does it: Here is the area where sortedmulti() logic is written. It just uses std::sort on a vector of CPubKey. Here is the logic for CPubKey comparison operators. vch is a 65 byte array with the first byte being 0x02|0x03|0x04 (so same format as we have here) and being compared... then memcmp is used to compare the rest (size() returns 33 when compressed, so it won't compare the 32 empty bytes in the array) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 24f0441
@junderw the Core logic looks very similar to my suggestion. :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm willing to accept this code if others say "we don't want to have SerializedPublicKey
". If others want SerializedPublicKey
then this would be duplication of code since that one would provide same thing with additional features.
However if others think that to_sort_key
returning type alias of SerializedPublicKey
is good for consumer code clarity, I'm also willing to accept this. :)
/// `SortKey` is not too useful by itself, but it can be used to sort a | ||
/// `[PublicKey]` slice using `sort_unstable_by_key`, `sort_by_cached_key`, | ||
/// `sort_by_key`, or any of the other `*_by_key` methods on slice. | ||
/// Pass the method into the sort method directly. (ie. `PublicKey::to_sort_key`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be nice to document that the conversion is non-trivial, so cached versions may perform better.
I kinda like the idea of having |
Since the link wasn't posted here I'll post a link to the |
tbh I think it would be better if SerializedPublicKey Derefed into bitcoin::PublicKey. Since we already would be manually impl all the Ord Eq etc. we might as well hold on to the bitcoin::PublicKey internally. |
Then it would be:
It will increase the data size of each key, but makes it more flexible, and we don't need to reimplement anything / worry about conversions when dealing with secp256k1 methods. |
Looking into it, it seems like most functionality on bitcoin::PublicKey serializes internally, so a simple Deref would leave us with an awkward inefficient API where SPK would be caching the serialization, but using one of the Derefed methods would serialize again and not make use of the cached serialization in SPK. |
Even if I agreed, it's impossible without ~doubling it's size. |
Hmm, but maybe methods for computing hashes of |
If I'm also not sure what |
I think everyone's vision of what SerializedPublicKey should be is all over the place and causing confusion. Currently, bitcoin::PublicKey can:
So SerializedPublicKey should:
In which case... we'd basically be replacing PublicKey completely... Maybe the SortKey and to_sort_key seems like a minimal non-breaking addition that provides a simple use case that isn't too performance critical to begin with. Perhaps we should just merge and continue discussion about the specifics of SerializedPublicKey for a breaking change? Thoughts? |
If we validate Serializing is basically free. Parsing and validation are not. If we were to have a |
@apoelstra |
So basically, what @apoelstra wants is something similar to what @Kixunil except without using Basically any instance would not be validated upon instantiation beyond a simple length check and maybe check that first byte is in the set [2,3,4] (Bitcoin Core also allows [6,7] which I am not sure of their meaning beyond the [2,3] bit patterns with a 4 bit added on) There is a public method on CPubKey that does simple validation (which checks that it isn't the It also looks like they add overrides to allow CPubKey to be used similar to its underlying data structure, so we should implement Deref<Target = [u8]> and Index< usize > We might as well just name it CPubKey, I guess. However, I wonder about the utility of some of the methods on Bitcoin Core's CPubKey for the port to this library. (some of them seem to be working around C++ limitations) |
Firstly, we're programming in Rust, not C++ so please avoid taking too much inspiration from Core design. I'm not sure if we should support directly converting arbitrary slices to
Those are exotic encodings we don't support here.
The code I reference indeed has
Please no. This is C++/Core naming convention ("C" meaning "class") and it could be confusing since we will not match it entirely. I'm not opposed to saying in the doc "This is similar, but not exactly same as
Exactly, I think we should stick to it being a heap-allocation-free replacement for |
I'm just trying to figure out what it is about the CPubKey design that we want to emulate besides the ordering. It (Core's CPubKey) validates signatures, verifies signatures, checks if signatures are lowS, recovers pubkeys from signatures+hashes into itself, derives child keys using bip32 by passing chain codes as an argument etc... all of which are... not things I would normally expect we'd want to emulate. My stance is still unchanged:
|
OK, I take back my willingness to mention
I think the chance is near zero. It has concept NACK from me and I'd need some very serious argument to consider it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 24f0441
On the basis that this can be type aliased anyway and perhaps the clarity is useful. The discussion derailed and it's not worth it holding this longer.
Sounds good to me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
post merge ACk 24f0441
…od for use with sorting by key 24f0441 Add PublicKey::to_sort_key method for use with sorting by key (junderw) Pull request description: Replaces #524 See previous PR for reasoning. This solution is a little more straightforward. The name and documentation should be enough to prevent misuse. We can also impl a to_sort_key for any CompressedKey added later. (or just impl Ord in a BIP67 compliant way) TODO: - [x] Add more sorting test vectors. Ideas of edge cases to test are welcome. ACKs for top commit: apoelstra: ACK 24f0441 tcharding: ACK 24f0441 Kixunil: ACK 24f0441 Tree-SHA512: 92d68cccaf32e224dd7328edeb3481dd7dcefb2f9090b7381e135e897c21f79ade922815cc766c5adb7ba751b71b51a773d103af6ba13fc081e1f5bfb846b201
Replaces #524
See previous PR for reasoning.
This solution is a little more straightforward. The name and documentation should be enough to prevent misuse.
We can also impl a to_sort_key for any CompressedKey added later. (or just impl Ord in a BIP67 compliant way)
TODO: