Skip to content

Commit 12ae655

Browse files
committed
Merge rust-bitcoin/rust-bitcoin#1084: Add PublicKey::to_sort_key method 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
2 parents e7e9716 + 56c797e commit 12ae655

File tree

1 file changed

+209
-2
lines changed

1 file changed

+209
-2
lines changed

src/util/key.rs

Lines changed: 209 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,72 @@ impl PublicKey {
172172
buf
173173
}
174174

175+
/// Serialize the public key into a `SortKey`.
176+
///
177+
/// `SortKey` is not too useful by itself, but it can be used to sort a
178+
/// `[PublicKey]` slice using `sort_unstable_by_key`, `sort_by_cached_key`,
179+
/// `sort_by_key`, or any of the other `*_by_key` methods on slice.
180+
/// Pass the method into the sort method directly. (ie. `PublicKey::to_sort_key`)
181+
///
182+
/// This method of sorting is in line with Bitcoin Core's implementation of
183+
/// sorting keys for output descriptors such as `sortedmulti()`.
184+
///
185+
/// If every `PublicKey` in the slice is `compressed == true` then this will sort
186+
/// the keys in a
187+
/// [BIP67](https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki)
188+
/// compliant way.
189+
///
190+
/// # Example: Using with `sort_unstable_by_key`
191+
///
192+
/// ```rust
193+
/// use std::str::FromStr;
194+
/// use bitcoin::PublicKey;
195+
///
196+
/// let pk = |s| PublicKey::from_str(s).unwrap();
197+
///
198+
/// let mut unsorted = [
199+
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35"),
200+
/// pk("038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354"),
201+
/// pk("028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa"),
202+
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa"),
203+
/// pk("032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b"),
204+
/// pk("045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8"),
205+
/// pk("0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68"),
206+
/// ];
207+
/// let sorted = [
208+
/// // These first 4 keys are in a BIP67 compatible sorted order
209+
/// // (since they are compressed)
210+
/// pk("0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68"),
211+
/// pk("028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa"),
212+
/// pk("032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b"),
213+
/// pk("038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354"),
214+
/// // Uncompressed keys are not BIP67 compliant, but are sorted
215+
/// // after compressed keys in Bitcoin Core using `sortedmulti()`
216+
/// pk("045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8"),
217+
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa"),
218+
/// pk("04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35"),
219+
/// ];
220+
///
221+
/// unsorted.sort_unstable_by_key(PublicKey::to_sort_key);
222+
///
223+
/// assert_eq!(unsorted, sorted);
224+
/// ```
225+
pub fn to_sort_key(&self) -> SortKey {
226+
if self.compressed {
227+
let bytes = self.inner.serialize();
228+
let mut res = [0; 32];
229+
res[..].copy_from_slice(&bytes[1..33]);
230+
SortKey(bytes[0], res, [0; 32])
231+
} else {
232+
let bytes = self.inner.serialize_uncompressed();
233+
let mut res_left = [0; 32];
234+
let mut res_right = [0; 32];
235+
res_left[..].copy_from_slice(&bytes[1..33]);
236+
res_right[..].copy_from_slice(&bytes[33..65]);
237+
SortKey(bytes[0], res_left, res_right)
238+
}
239+
}
240+
175241
/// Deserialize a public key from a slice
176242
pub fn from_slice(data: &[u8]) -> Result<PublicKey, Error> {
177243
let compressed = match data.len() {
@@ -198,6 +264,10 @@ impl PublicKey {
198264
}
199265
}
200266

267+
/// An opaque return type for PublicKey::to_sort_key
268+
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
269+
pub struct SortKey(u8, [u8; 32], [u8; 32]);
270+
201271
impl fmt::Display for PublicKey {
202272
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203273
if self.compressed {
@@ -475,10 +545,10 @@ impl<'de> ::serde::Deserialize<'de> for PublicKey {
475545
#[cfg(test)]
476546
mod tests {
477547
use crate::io;
478-
use super::{PrivateKey, PublicKey};
548+
use super::{PrivateKey, PublicKey, SortKey};
479549
use secp256k1::Secp256k1;
480550
use std::str::FromStr;
481-
use crate::hashes::hex::ToHex;
551+
use crate::hashes::hex::{FromHex, ToHex};
482552
use crate::network::constants::Network::Testnet;
483553
use crate::network::constants::Network::Bitcoin;
484554
use crate::util::address::Address;
@@ -629,4 +699,141 @@ mod tests {
629699
assert!(PublicKey::read_from(io::Cursor::new(&[0; 65][..])).is_err());
630700
assert!(PublicKey::read_from(io::Cursor::new(&[4; 64][..])).is_err());
631701
}
702+
703+
#[test]
704+
fn pubkey_to_sort_key() {
705+
let key1 = PublicKey::from_str("02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8").unwrap();
706+
let key2 = PublicKey {
707+
inner: key1.inner,
708+
compressed: false,
709+
};
710+
let expected1 = SortKey(
711+
2,
712+
<[u8; 32]>::from_hex(
713+
"ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8",
714+
).unwrap(),
715+
[0_u8; 32],
716+
);
717+
let expected2 = SortKey(
718+
4,
719+
<[u8; 32]>::from_hex(
720+
"ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8",
721+
).unwrap(),
722+
<[u8; 32]>::from_hex(
723+
"1794e7f3d5e420641a3bc690067df5541470c966cbca8c694bf39aa16d836918",
724+
).unwrap(),
725+
);
726+
assert_eq!(key1.to_sort_key(), expected1);
727+
assert_eq!(key2.to_sort_key(), expected2);
728+
}
729+
730+
#[test]
731+
fn pubkey_sort() {
732+
struct Vector {
733+
input: Vec<PublicKey>,
734+
expect: Vec<PublicKey>,
735+
}
736+
let fmt = |v: Vec<_>| v.into_iter()
737+
.map(|s| PublicKey::from_str(s).unwrap())
738+
.collect::<Vec<_>>();
739+
let vectors = vec![
740+
// Start BIP67 vectors
741+
// Vector 1
742+
Vector {
743+
input: fmt(vec![
744+
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8",
745+
"02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f",
746+
]),
747+
expect: fmt(vec![
748+
"02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f",
749+
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8",
750+
]),
751+
},
752+
// Vector 2 (Already sorted, no action required)
753+
Vector {
754+
input: fmt(vec![
755+
"02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0",
756+
"027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77",
757+
"02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404",
758+
]),
759+
expect: fmt(vec![
760+
"02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0",
761+
"027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77",
762+
"02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404",
763+
]),
764+
},
765+
// Vector 3
766+
Vector {
767+
input: fmt(vec![
768+
"030000000000000000000000000000000000004141414141414141414141414141",
769+
"020000000000000000000000000000000000004141414141414141414141414141",
770+
"020000000000000000000000000000000000004141414141414141414141414140",
771+
"030000000000000000000000000000000000004141414141414141414141414140",
772+
]),
773+
expect: fmt(vec![
774+
"020000000000000000000000000000000000004141414141414141414141414140",
775+
"020000000000000000000000000000000000004141414141414141414141414141",
776+
"030000000000000000000000000000000000004141414141414141414141414140",
777+
"030000000000000000000000000000000000004141414141414141414141414141",
778+
]),
779+
},
780+
// Vector 4: (from bitcore)
781+
Vector {
782+
input: fmt(vec![
783+
"022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da",
784+
"03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9",
785+
"021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18",
786+
]),
787+
expect: fmt(vec![
788+
"021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18",
789+
"022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da",
790+
"03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9",
791+
]),
792+
},
793+
// Non-BIP67 vectors
794+
Vector {
795+
input: fmt(vec![
796+
"02c690d642c1310f3a1ababad94e3930e4023c930ea472e7f37f660fe485263b88",
797+
"0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68",
798+
"041a181bd0e79974bd7ca552e09fc42ba9c3d5dbb3753741d6f0ab3015dbfd9a22d6b001a32f5f51ac6f2c0f35e73a6a62f59e848fa854d3d21f3f231594eeaa46",
799+
"032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b",
800+
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa",
801+
"028e1c947c8c0b8ed021088b8e981491ac7af2b8fabebea1abdb448424c8ed75b7",
802+
"045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8",
803+
"03004a8a3d242d7957c0b60fb7208d386fa6a0193aabd1f3f095ffd0ac097e447b",
804+
"04eb0db2d71ccbb0edd8fb35092cbcae2f7fa1f06d4c170804bf52007924b569a8d2d6f6bc8fd2b3caa3253fa1bb674443743bf7fb9f94f9c0b0831a252894cfa8",
805+
"04516cde23e14f2319423b7a4a7ae48b1dadceb5e9c123198d417d10895684c42eb05e210f90ccbc72448803a22312e3f122ff2939956ccef4f7316f836295ddd5",
806+
"038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354",
807+
"04c6bec3b07586a4b085a78cbb97e9bab6f1d3c9ebf299b65dec85213c5eacd44487de86017183120bb7ea3b6c6660c5037615fe1add2a73f800cbeeae22c60438",
808+
"03e1a1cfa9eaff604ae237b7af31ffe4c01be22eb96f3da0e62c5850dd4b4386c1",
809+
"028d3a2d9f1b1c5c75845944f93bc183ba23aecde53f1978b8aa1b77661be6114f",
810+
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa",
811+
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35",
812+
]),
813+
expect: fmt(vec![
814+
"0234dd69c56c36a41230d573d68adeae0030c9bc0bf26f24d3e1b64c604d293c68",
815+
"028bde91b10013e08949a318018fedbd896534a549a278e220169ee2a36517c7aa",
816+
"028d3a2d9f1b1c5c75845944f93bc183ba23aecde53f1978b8aa1b77661be6114f",
817+
"028e1c947c8c0b8ed021088b8e981491ac7af2b8fabebea1abdb448424c8ed75b7",
818+
"02c690d642c1310f3a1ababad94e3930e4023c930ea472e7f37f660fe485263b88",
819+
"03004a8a3d242d7957c0b60fb7208d386fa6a0193aabd1f3f095ffd0ac097e447b",
820+
"032b8324c93575034047a52e9bca05a46d8347046b91a032eff07d5de8d3f2730b",
821+
"038f47dcd43ba6d97fc9ed2e3bba09b175a45fac55f0683e8cf771e8ced4572354",
822+
"03e1a1cfa9eaff604ae237b7af31ffe4c01be22eb96f3da0e62c5850dd4b4386c1",
823+
"041a181bd0e79974bd7ca552e09fc42ba9c3d5dbb3753741d6f0ab3015dbfd9a22d6b001a32f5f51ac6f2c0f35e73a6a62f59e848fa854d3d21f3f231594eeaa46",
824+
"04516cde23e14f2319423b7a4a7ae48b1dadceb5e9c123198d417d10895684c42eb05e210f90ccbc72448803a22312e3f122ff2939956ccef4f7316f836295ddd5",
825+
"045d753414fa292ea5b8f56e39cfb6a0287b2546231a5cb05c4b14ab4b463d171f5128148985b23eccb1e2905374873b1f09b9487f47afa6b1f2b0083ac8b4f7e8",
826+
// These two pubkeys are mirrored. This helps verify the sort past the x value.
827+
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc3816753d96001fd7cba3ce5372f5c9a0d63708183033538d07b1e532fc43aaacfa",
828+
"04c4b0bbb339aa236bff38dbe6a451e111972a7909a126bc424013cba2ec33bc38e98ac269ffe028345c31ac8d0a365f29c8f7e7cfccac72f84e1acd02bc554f35",
829+
"04c6bec3b07586a4b085a78cbb97e9bab6f1d3c9ebf299b65dec85213c5eacd44487de86017183120bb7ea3b6c6660c5037615fe1add2a73f800cbeeae22c60438",
830+
"04eb0db2d71ccbb0edd8fb35092cbcae2f7fa1f06d4c170804bf52007924b569a8d2d6f6bc8fd2b3caa3253fa1bb674443743bf7fb9f94f9c0b0831a252894cfa8",
831+
]),
832+
},
833+
];
834+
for mut vector in vectors {
835+
vector.input.sort_by_cached_key(PublicKey::to_sort_key);
836+
assert_eq!(vector.input, vector.expect);
837+
}
838+
}
632839
}

0 commit comments

Comments
 (0)