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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -182,6 +182,72 @@ impl PublicKey { | |
buf | ||
} | ||
|
||
/// Serialize the public key into a `SortKey`. | ||
/// | ||
/// `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`) | ||
/// | ||
/// This method of sorting is in line with Bitcoin Core's implementation of | ||
/// sorting keys for output descriptors such as `sortedmulti()`. | ||
/// | ||
/// If every `PublicKey` in the slice is `compressed == true` then this will sort | ||
junderw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// the keys in a | ||
/// [BIP67](https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki) | ||
/// compliant way. | ||
/// | ||
/// # Example: Using with `sort_unstable_by_key` | ||
/// | ||
/// ```rust | ||
/// use std::str::FromStr; | ||
/// use bitcoin::PublicKey; | ||
/// | ||
/// let pk = |s| PublicKey::from_str(s).unwrap(); | ||
/// | ||
/// let mut unsorted = [ | ||
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35"), | ||
/// pk("038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354"), | ||
/// pk("028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa"), | ||
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa"), | ||
/// pk("032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b"), | ||
/// pk("045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8"), | ||
/// pk("0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68"), | ||
/// ]; | ||
/// let sorted = [ | ||
/// // These first 4 keys are in a BIP67 compatible sorted order | ||
/// // (since they are compressed) | ||
/// pk("0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68"), | ||
/// pk("028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa"), | ||
/// pk("032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b"), | ||
/// pk("038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354"), | ||
/// // Uncompressed keys are not BIP67 compliant, but are sorted | ||
/// // after compressed keys in Bitcoin Core using `sortedmulti()` | ||
/// pk("045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8"), | ||
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa"), | ||
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35"), | ||
/// ]; | ||
/// | ||
/// unsorted.sort_unstable_by_key(PublicKey::to_sort_key); | ||
/// | ||
/// assert_eq!(unsorted, sorted); | ||
/// ``` | ||
pub fn to_sort_key(&self) -> SortKey { | ||
if self.compressed { | ||
let bytes = self.inner.serialize(); | ||
let mut res = [0; 32]; | ||
res[..].copy_from_slice(&bytes[1..33]); | ||
SortKey(bytes[0], res, [0; 32]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For #1060 we could use slice patterns here: |
||
} else { | ||
let bytes = self.inner.serialize_uncompressed(); | ||
let mut res_left = [0; 32]; | ||
let mut res_right = [0; 32]; | ||
res_left[..].copy_from_slice(&bytes[1..33]); | ||
res_right[..].copy_from_slice(&bytes[33..65]); | ||
SortKey(bytes[0], res_left, res_right) | ||
} | ||
} | ||
|
||
/// Deserialize a public key from a slice | ||
pub fn from_slice(data: &[u8]) -> Result<PublicKey, Error> { | ||
let compressed = match data.len() { | ||
|
@@ -208,6 +274,10 @@ impl PublicKey { | |
} | ||
} | ||
|
||
/// An opaque return type for PublicKey::to_sort_key | ||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
pub struct SortKey(u8, [u8; 32], [u8; 32]); | ||
|
||
impl fmt::Display for PublicKey { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
if self.compressed { | ||
|
@@ -485,10 +555,10 @@ impl<'de> ::serde::Deserialize<'de> for PublicKey { | |
#[cfg(test)] | ||
mod tests { | ||
use crate::io; | ||
use super::{PrivateKey, PublicKey}; | ||
use super::{PrivateKey, PublicKey, SortKey}; | ||
use secp256k1::Secp256k1; | ||
use std::str::FromStr; | ||
use crate::hashes::hex::ToHex; | ||
use crate::hashes::hex::{FromHex, ToHex}; | ||
use crate::network::constants::Network::Testnet; | ||
use crate::network::constants::Network::Bitcoin; | ||
use crate::util::address::Address; | ||
|
@@ -639,4 +709,141 @@ mod tests { | |
assert!(PublicKey::read_from(io::Cursor::new(&[0; 65][..])).is_err()); | ||
assert!(PublicKey::read_from(io::Cursor::new(&[4; 64][..])).is_err()); | ||
} | ||
|
||
#[test] | ||
fn pubkey_to_sort_key() { | ||
let key1 = PublicKey::from_str("02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8").unwrap(); | ||
let key2 = PublicKey { | ||
inner: key1.inner, | ||
compressed: false, | ||
}; | ||
let expected1 = SortKey( | ||
2, | ||
<[u8; 32]>::from_hex( | ||
"ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8", | ||
).unwrap(), | ||
[0_u8; 32], | ||
); | ||
let expected2 = SortKey( | ||
4, | ||
<[u8; 32]>::from_hex( | ||
"ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8", | ||
).unwrap(), | ||
<[u8; 32]>::from_hex( | ||
"1794e7f3d5e420641a3bc690067df5541470c966cbca8c694bf39aa16d836918", | ||
).unwrap(), | ||
); | ||
assert_eq!(key1.to_sort_key(), expected1); | ||
assert_eq!(key2.to_sort_key(), expected2); | ||
} | ||
|
||
#[test] | ||
fn pubkey_sort() { | ||
struct Vector { | ||
input: Vec<PublicKey>, | ||
expect: Vec<PublicKey>, | ||
} | ||
let fmt = |v: Vec<_>| v.into_iter() | ||
.map(|s| PublicKey::from_str(s).unwrap()) | ||
.collect::<Vec<_>>(); | ||
let vectors = vec![ | ||
// Start BIP67 vectors | ||
// Vector 1 | ||
Vector { | ||
input: fmt(vec![ | ||
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8", | ||
"02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f", | ||
]), | ||
expect: fmt(vec![ | ||
"02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f", | ||
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8", | ||
]), | ||
}, | ||
// Vector 2 (Already sorted, no action required) | ||
Vector { | ||
input: fmt(vec![ | ||
"02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0", | ||
"027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77", | ||
"02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404", | ||
]), | ||
expect: fmt(vec![ | ||
"02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0", | ||
"027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77", | ||
"02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404", | ||
]), | ||
}, | ||
// Vector 3 | ||
Vector { | ||
input: fmt(vec![ | ||
"030000000000000000000000000000000000004141414141414141414141414141", | ||
"020000000000000000000000000000000000004141414141414141414141414141", | ||
"020000000000000000000000000000000000004141414141414141414141414140", | ||
"030000000000000000000000000000000000004141414141414141414141414140", | ||
]), | ||
expect: fmt(vec![ | ||
"020000000000000000000000000000000000004141414141414141414141414140", | ||
"020000000000000000000000000000000000004141414141414141414141414141", | ||
"030000000000000000000000000000000000004141414141414141414141414140", | ||
"030000000000000000000000000000000000004141414141414141414141414141", | ||
]), | ||
}, | ||
// Vector 4: (from bitcore) | ||
Vector { | ||
input: fmt(vec![ | ||
"022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da", | ||
"03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9", | ||
"021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18", | ||
]), | ||
expect: fmt(vec![ | ||
"021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18", | ||
"022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da", | ||
"03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9", | ||
]), | ||
}, | ||
// Non-BIP67 vectors | ||
Vector { | ||
input: fmt(vec![ | ||
"02c690d642c1310f3a1ababad94e3930e4023c930ea472e7f37f660fe485263b88", | ||
"0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68", | ||
"041a181bd0e79974bd7ca552e09fc42ba9c3d5dbb3753741d6f0ab3015dbfd9a22d6b001a32f5f51ac6f2c0f35e73a6a62f59e848fa854d3d21f3f231594eeaa46", | ||
"032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b", | ||
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa", | ||
"028e1c947c8c0b8ed021088b8e981491ac7af2b8fabebea1abdb448424c8ed75b7", | ||
"045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8", | ||
"03004a8a3d242d7957c0b60fb7208d386fa6a0193aabd1f3f095ffd0ac097e447b", | ||
"04eb0db2d71ccbb0edd8fb35092cbcae2f7fa1f06d4c170804bf52007924b569a8d2d6f6bc8fd2b3caa3253fa1bb674443743bf7fb9f94f9c0b0831a252894cfa8", | ||
"04516cde23e14f2319423b7a4a7ae48b1dadceb5e9c123198d417d10895684c42eb05e210f90ccbc72448803a22312e3f122ff2939956ccef4f7316f836295ddd5", | ||
"038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354", | ||
"04c6bec3b07586a4b085a78cbb97e9bab6f1d3c9ebf299b65dec85213c5eacd44487de86017183120bb7ea3b6c6660c5037615fe1add2a73f800cbeeae22c60438", | ||
"03e1a1cfa9eaff604ae237b7af31ffe4c01be22eb96f3da0e62c5850dd4b4386c1", | ||
"028d3a2d9f1b1c5c75845944f93bc183ba23aecde53f1978b8aa1b77661be6114f", | ||
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa", | ||
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35", | ||
]), | ||
expect: fmt(vec![ | ||
"0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68", | ||
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa", | ||
"028d3a2d9f1b1c5c75845944f93bc183ba23aecde53f1978b8aa1b77661be6114f", | ||
"028e1c947c8c0b8ed021088b8e981491ac7af2b8fabebea1abdb448424c8ed75b7", | ||
"02c690d642c1310f3a1ababad94e3930e4023c930ea472e7f37f660fe485263b88", | ||
"03004a8a3d242d7957c0b60fb7208d386fa6a0193aabd1f3f095ffd0ac097e447b", | ||
"032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b", | ||
"038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354", | ||
"03e1a1cfa9eaff604ae237b7af31ffe4c01be22eb96f3da0e62c5850dd4b4386c1", | ||
"041a181bd0e79974bd7ca552e09fc42ba9c3d5dbb3753741d6f0ab3015dbfd9a22d6b001a32f5f51ac6f2c0f35e73a6a62f59e848fa854d3d21f3f231594eeaa46", | ||
"04516cde23e14f2319423b7a4a7ae48b1dadceb5e9c123198d417d10895684c42eb05e210f90ccbc72448803a22312e3f122ff2939956ccef4f7316f836295ddd5", | ||
"045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8", | ||
// These two pubkeys are mirrored. This helps verify the sort past the x value. | ||
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa", | ||
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35", | ||
"04c6bec3b07586a4b085a78cbb97e9bab6f1d3c9ebf299b65dec85213c5eacd44487de86017183120bb7ea3b6c6660c5037615fe1add2a73f800cbeeae22c60438", | ||
"04eb0db2d71ccbb0edd8fb35092cbcae2f7fa1f06d4c170804bf52007924b569a8d2d6f6bc8fd2b3caa3253fa1bb674443743bf7fb9f94f9c0b0831a252894cfa8", | ||
]), | ||
}, | ||
]; | ||
for mut vector in vectors { | ||
vector.input.sort_by_cached_key(PublicKey::to_sort_key); | ||
assert_eq!(vector.input, vector.expect); | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.