Skip to content
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 1 commit into from Jul 13, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
211 changes: 209 additions & 2 deletions src/util/key.rs
Expand Up @@ -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`)
Copy link
Collaborator

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.

///
/// 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])
Copy link
Collaborator

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])

} 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() {
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
}