diff --git a/src/util/key.rs b/src/util/key.rs index a2c6a860..99451290 100644 --- a/src/util/key.rs +++ b/src/util/key.rs @@ -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 + /// 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]) + } 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 { 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, + expect: Vec, + } + let fmt = |v: Vec<_>| v.into_iter() + .map(|s| PublicKey::from_str(s).unwrap()) + .collect::>(); + 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); + } + } }