diff --git a/Cargo.lock b/Cargo.lock index 20ebc88..6d305ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,10 +1,34 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "blake3" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "c2-chacha" version = "0.2.3" @@ -13,11 +37,46 @@ dependencies = [ "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cc" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -28,11 +87,40 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "leb128" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.2.11" @@ -83,14 +171,29 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "stable-hash" -version = "0.1.0" +version = "0.2.0" dependencies = [ + "blake3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "leb128 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "twox-hash" version = "1.5.0" @@ -99,22 +202,42 @@ dependencies = [ "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum blake3 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "68df31bdf2bbb567e5adf8f21ac125dc0e897b1381e7b841f181521f06fc3134" "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" +"checksum cc 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)" = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +"checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum leb128 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" "checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +"checksum num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56" +"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" diff --git a/Cargo.toml b/Cargo.toml index 52586d5..1daa0f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "stable-hash" -version = "0.1.0" +version = "0.2.0" authors = ["Zac Burns "] edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +blake3 = "0.3.3" num-traits = "0.2.11" +leb128 = "0.2.4" +num-bigint = "0.2.6" +lazy_static = "1.4.0" [dev-dependencies] -twox-hash = "1.5.0" \ No newline at end of file +twox-hash = "1.5.0" +rustc-hex = "2.1.0" \ No newline at end of file diff --git a/src/crypto/blake3_sequence.rs b/src/crypto/blake3_sequence.rs new file mode 100644 index 0000000..ee7273b --- /dev/null +++ b/src/crypto/blake3_sequence.rs @@ -0,0 +1,48 @@ +use crate::prelude::*; +use blake3::{Hasher, OutputReader}; +use leb128::write::unsigned as write_varint; +use std::convert::TryInto as _; +use std::num::NonZeroUsize; + +#[derive(Clone)] +pub struct Blake3SeqNo { + hasher: Hasher, + // This has to be NonZero in order to be injective, since the payload marker writes 0 + // See also 91e48829-7bea-4426-971a-f092856269a5 + child: NonZeroUsize, +} + +impl SequenceNumber for Blake3SeqNo { + fn root() -> Self { + Self { + hasher: Hasher::new(), + child: NonZeroUsize::new(1).unwrap(), + } + } + fn next_child(&mut self) -> Self { + let child = self.child; + let mut hasher = self.hasher.clone(); + // Better to panic than overflow. + self.child = NonZeroUsize::new(child.get() + 1).unwrap(); + // Include the child node + write_varint(&mut hasher, child.get().try_into().unwrap()).unwrap(); + Self { + hasher, + child: NonZeroUsize::new(1).unwrap(), + } + } + #[inline] + fn skip(&mut self, count: usize) { + self.child = NonZeroUsize::new(self.child.get() + count).unwrap(); + } +} + +impl Blake3SeqNo { + pub(crate) fn finish(self, payload: &[u8]) -> OutputReader { + let Self { mut hasher, .. } = self; + // See also 91e48829-7bea-4426-971a-f092856269a5 + hasher.update(&[0]); + hasher.update(payload); + hasher.finalize_xof() + } +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..9cc3b9a --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,5 @@ +mod blake3_sequence; +mod set_hasher; + +pub use blake3_sequence::Blake3SeqNo; +pub use set_hasher::SetHasher; diff --git a/src/crypto/set_hasher.rs b/src/crypto/set_hasher.rs new file mode 100644 index 0000000..48b7d1d --- /dev/null +++ b/src/crypto/set_hasher.rs @@ -0,0 +1,100 @@ +use super::blake3_sequence::Blake3SeqNo; +use crate::prelude::*; +use crate::stable_hash::UnorderedAggregator; +use blake3::Hasher; +use lazy_static::lazy_static; +use num_bigint::BigUint; +use num_traits::identities::One; +use std::default::Default; + +lazy_static! { + static ref P: BigUint = "50763434429823703141085322590076158163032399096130816327134180611270739679038131809123861970975131471260684737408234060876742190838745219274061025048845231234136148410311444604554192918702297959809128216170781389312847013812749872750274650041183009144583521632294518996531883338553737214586176414455965584933129379474747808392433032576309945590584603359054260866543918929486383805924215982747035136255123252119828736134723149397165643360162699752374292974151421555939481822911026769138419707577501643119472226283015793622652706604535623136902831581637275314074553942039263472515423713366344495524733341031029964603383".parse().unwrap(); +} + +/// Based on https://crypto.stackexchange.com/a/54546 +/// +/// The idea here is to use the SequenceNumber to unambiguously identify each +/// field as within it's own database cell, and use an online order-independent +/// aggregator of the cells to produce a final result. +/// +/// Within this framework a huge struct can be hashed incrementally or even in +/// parallel as long as sequence numbers are deterministically produced to +/// identify parts within the struct. Conveniently, the SequenceNumber::skip +/// method can be used to jump to parts of a vec or struct efficiently. +pub struct SetHasher { + // TODO: (Performance). We want an int 2056 + 2048 = 4104 bit int. + // That's enough to handle any sequence of mixin operations without overflow. + // https://github.com/paritytech/parity-common/issues/388 + // Not a bad idea to start here so that when we convert we know that the transformation is ok. + value: BigUint, +} + +impl Default for SetHasher { + fn default() -> Self { + Self { + value: BigUint::one(), + } + } +} + +impl SetHasher { + #[inline] + pub fn new() -> Self { + Default::default() + } + #[inline] + fn mixin(&mut self, digits: &BigUint) { + self.value = (&self.value * digits) % &*P; + } + pub fn to_bytes(&self) -> Vec { + self.value.to_bytes_le() + } + /// Panics if the bytes are not in a valid format. + /// The only valid values are values returned from to_bytes() + pub fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() <= 257); + let value = BigUint::from_bytes_le(bytes); + Self { value } + } +} + +/// The SetHasher is already updated in an unordered fashion, so no special second struct +/// is needed. Starts at 1 and mixin when finished. +impl UnorderedAggregator for SetHasher { + #[inline] + fn write(&mut self, value: impl StableHash, sequence_number: Blake3SeqNo) { + value.stable_hash(sequence_number, self) + } +} + +impl StableHasher for SetHasher { + type Out = [u8; 32]; + type Seq = Blake3SeqNo; + type Unordered = Self; + fn write(&mut self, sequence_number: Self::Seq, bytes: &[u8]) { + // Write the field into a database cell + let mut output = sequence_number.finish(bytes); + // Extend to the length necessary. This is a 2048 bit value, 1 bit + // less than the prime the hash wraps around. + let mut digits = [0u8; 256]; + output.fill(&mut digits); + let digits = BigUint::from_bytes_le(&digits); + // Add the value to the database + self.mixin(&digits) + } + #[inline] + fn start_unordered(&mut self) -> Self::Unordered { + Self::new() + } + #[inline] + fn finish_unordered(&mut self, unordered: Self::Unordered, _sequence_number: Self::Seq) { + self.mixin(&unordered.value) + } + fn finish(&self) -> Self::Out { + // Re-mix the state with a Hasher. + let mut hasher = Hasher::new(); + let le = self.value.to_bytes_le(); + hasher.update(&le); + hasher.finalize().into() + } +} diff --git a/src/impls/bool.rs b/src/impls/bool.rs index 0d3c2a3..e6e3302 100644 --- a/src/impls/bool.rs +++ b/src/impls/bool.rs @@ -1,7 +1,7 @@ use crate::prelude::*; impl StableHash for bool { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { if *self { state.write(sequence_number, &[]); } diff --git a/src/impls/hash_map.rs b/src/impls/hash_map.rs index f9c687e..dbc613d 100644 --- a/src/impls/hash_map.rs +++ b/src/impls/hash_map.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use std::collections::HashMap; impl StableHash for HashMap { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { - super::unordered_stable_hash(self.iter(), sequence_number, state) + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { + super::unordered_unique_stable_hash(self.iter(), sequence_number, state) } } diff --git a/src/impls/hash_set.rs b/src/impls/hash_set.rs index e88309c..9c7f040 100644 --- a/src/impls/hash_set.rs +++ b/src/impls/hash_set.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use std::collections::HashSet; impl StableHash for HashSet { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { - super::unordered_stable_hash(self.iter(), sequence_number, state) + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { + super::unordered_unique_stable_hash(self.iter(), sequence_number, state) } } diff --git a/src/impls/ints.rs b/src/impls/ints.rs index fa080b5..ac23bfa 100644 --- a/src/impls/ints.rs +++ b/src/impls/ints.rs @@ -3,11 +3,7 @@ use crate::prelude::*; macro_rules! impl_int { ($P:ty, $N:ty) => { impl StableHash for $P { - fn stable_hash( - &self, - sequence_number: impl SequenceNumber, - state: &mut impl StableHasher, - ) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { AsInt { is_negative: false, little_endian: &self.to_le_bytes(), @@ -16,11 +12,7 @@ macro_rules! impl_int { } } impl StableHash for $N { - fn stable_hash( - &self, - sequence_number: impl SequenceNumber, - state: &mut impl StableHasher, - ) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { AsInt { is_negative: self.is_negative(), little_endian: &self.wrapping_abs().to_le_bytes(), diff --git a/src/impls/mod.rs b/src/impls/mod.rs index c33deb9..2232b5e 100644 --- a/src/impls/mod.rs +++ b/src/impls/mod.rs @@ -9,26 +9,32 @@ mod tuple; mod vec; use crate::prelude::*; +use crate::stable_hash::UnorderedAggregator; -pub(self) fn unordered_stable_hash( +pub(self) fn unordered_unique_stable_hash( items: impl Iterator, - mut sequence_number: S, + mut sequence_number: H::Seq, state: &mut H, ) { - let mut rollup = H::Out::default(); + // First, create child nodes for each element. + // Doing this here removes any opportunity for collisions + let rollup_seq_no = sequence_number.next_child(); + let member_seq_no = sequence_number.next_child(); + let count_seq_no = sequence_number.next_child(); + + let mut unordered = state.start_unordered(); let mut count = 0usize; for member in items { - let mut hasher = H::default(); - member.stable_hash(S::root(), &mut hasher); - rollup ^= hasher.finish(); + unordered.write(member, member_seq_no.clone()); count += 1; } - rollup.stable_hash(sequence_number.next_child(), state); - count.stable_hash(sequence_number, state); + state.finish_unordered(unordered, rollup_seq_no); + count.stable_hash(count_seq_no, state); } impl<'a, T: StableHash> StableHash for &'a T { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + #[inline] + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { (*self).stable_hash(sequence_number, state) } } diff --git a/src/impls/option.rs b/src/impls/option.rs index bc6173b..ddb4fea 100644 --- a/src/impls/option.rs +++ b/src/impls/option.rs @@ -1,7 +1,7 @@ use crate::prelude::*; impl StableHash for Option { - fn stable_hash(&self, mut sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, mut sequence_number: H::Seq, state: &mut H) { self.is_some() .stable_hash(sequence_number.next_child(), state); if let Some(value) = self { diff --git a/src/impls/string.rs b/src/impls/string.rs index 2a13330..91c1c6e 100644 --- a/src/impls/string.rs +++ b/src/impls/string.rs @@ -1,13 +1,13 @@ use crate::prelude::*; impl StableHash for String { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { self.as_str().stable_hash(sequence_number, state); } } impl<'a> StableHash for &'a str { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { AsBytes(self.as_bytes()).stable_hash(sequence_number, state) } } diff --git a/src/impls/tuple.rs b/src/impls/tuple.rs index abadd4c..4159e5e 100644 --- a/src/impls/tuple.rs +++ b/src/impls/tuple.rs @@ -4,7 +4,7 @@ macro_rules! impl_tuple { ($($T:ident),*) => { impl<$($T : StableHash,)*> StableHash for ($($T,)*) { #[allow(non_snake_case)] - fn stable_hash(&self, mut sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, mut sequence_number: H::Seq, state: &mut H) { let ($($T,)*) = self; $( @@ -23,4 +23,4 @@ macro_rules! impl_tuples { } } -impl_tuples!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); \ No newline at end of file +impl_tuples!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); diff --git a/src/impls/vec.rs b/src/impls/vec.rs index d48a127..57cc124 100644 --- a/src/impls/vec.rs +++ b/src/impls/vec.rs @@ -1,13 +1,13 @@ use crate::prelude::*; impl StableHash for Vec { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { (&self[..]).stable_hash(sequence_number, state) } } impl<'a, T: StableHash> StableHash for &'a [T] { - fn stable_hash(&self, mut sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, mut sequence_number: H::Seq, state: &mut H) { for item in self.iter() { item.stable_hash(sequence_number.next_child(), state); } diff --git a/src/lib.rs b/src/lib.rs index 3cf83c8..0d26def 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod crypto; mod impls; pub mod prelude; mod sequence_number; diff --git a/src/sequence_number.rs b/src/sequence_number.rs index 89c442c..e1b1625 100644 --- a/src/sequence_number.rs +++ b/src/sequence_number.rs @@ -1,58 +1,163 @@ use std::borrow::Borrow; -use std::num::Wrapping; +use std::convert::{TryFrom, TryInto}; -pub trait SequenceNumber { - type Rollup: Borrow<[u8]>; - fn rollup(&self) -> Self::Rollup; - fn root() -> Self - where - Self: Sized; - fn next_child(&mut self) -> Self - where - Self: Sized; +pub trait UInt: TryFrom + Copy { + type Bytes: Borrow<[u8]>; + fn prime_init() -> Self; + fn prime_mult() -> Self; + fn to_le_bytes(self) -> Self::Bytes; + fn wrapping_add(self, other: Self) -> Self; + fn wrapping_mul(self, other: Self) -> Self; } -#[derive(Debug)] +pub trait SequenceNumber: Clone { + fn root() -> Self; + fn next_child(&mut self) -> Self; + fn skip(&mut self, count: usize) { + for _ in 0..count { + self.next_child(); + } + } +} + +#[derive(Debug, Clone)] pub struct SequenceNumberInt { - rollup: Wrapping, + rollup: T, child: usize, } -macro_rules! impl_sequence_no { - ($T:ty, $size:expr, $prime_init:expr, $prime_mult:expr) => { - impl Default for SequenceNumberInt<$T> { - fn default() -> Self { - Self::root() - } +impl Default for SequenceNumberInt { + #[inline(always)] + fn default() -> Self { + Self::root() + } +} + +impl SequenceNumberInt { + pub fn rollup(&self) -> T { + self.rollup + } +} + +impl SequenceNumber for SequenceNumberInt { + fn root() -> Self { + Self { + rollup: T::prime_init(), + child: 0, } - impl SequenceNumber for SequenceNumberInt<$T> { - type Rollup = [u8; $size]; - fn root() -> Self { - Self { - rollup: Wrapping($prime_init), - child: 0, - } - } + } + #[inline] + fn next_child(&mut self) -> Self { + let child = self.child; + self.child += 1; - #[inline] - fn next_child(&mut self) -> Self { - let child = self.child; - self.child += 1; + let rollup = self + .rollup + .wrapping_mul(T::prime_mult()) + .wrapping_add(child.try_into().unwrap_or_else(|_| panic!("Overflow"))); - let rollup = (self.rollup * Wrapping($prime_mult)) + Wrapping(child as $T); + Self { rollup, child: 0 } + } + #[inline] + fn skip(&mut self, count: usize) { + self.child += count; + } +} - Self { rollup, child: 0 } +macro_rules! impl_sequence_no { + ($T:ty, $size:expr) => { + impl UInt for $T { + type Bytes = [u8; $size]; + #[inline(always)] + fn prime_init() -> Self { + 17 } - - #[inline] - fn rollup(&self) -> Self::Rollup { - self.rollup.0.to_le_bytes() + #[inline(always)] + fn prime_mult() -> Self { + 486_187_739 + } + #[inline(always)] + fn wrapping_add(self, other: Self) -> Self { + self.wrapping_add(other) + } + #[inline(always)] + fn wrapping_mul(self, other: Self) -> Self { + self.wrapping_mul(other) + } + #[inline(always)] + fn to_le_bytes(self) -> Self::Bytes { + self.to_le_bytes() } } }; } -// These values are locked in! -// Don't change them. Ever. -impl_sequence_no!(u64, 8, 17, 486_187_739); -impl_sequence_no!(u32, 4, 17, 486_187_739); +impl_sequence_no!(u64, 8); +impl_sequence_no!(u32, 4); + +#[cfg(test)] +mod test { + use super::{SequenceNumber, SequenceNumberInt, UInt}; + + use std::collections::HashSet; + use std::hash::Hash; + + fn recurse( + mut sequence_number: SequenceNumberInt, + depth: usize, + length: usize, + collector: &mut HashSet, + ) { + // Struct/Recursion check + for _ in 0..6 { + let child = sequence_number.next_child(); + assert!(collector.insert(child.rollup())); + if depth != 0 { + recurse(child, depth - 1, length, collector); + } + } + // Vec check (not recursive) + // Tests larger vecs closer to the root, where larger vecs are more likely + for _ in 0..(length * depth * depth) { + let child = sequence_number.next_child(); + assert!(collector.insert(child.rollup())); + } + } + + /// This test demonstrates that our choice of primes and algorithm is a good + /// one for our use case of common structures to be digested by trying every + /// permutation of all structs several deep and long Vecs for children and + /// asserting 0 collisions on over 11 million common SequenceNumber:: + /// paths and almost 3.4 million SequenceNumber:: paths. Just for kicks I + /// ran it on over 700 million paths before committing, but this test did + /// not complete in a reasonable enough amount of time to be committed. + /// Larger than that and we get dangerously close to birthday collisions + /// anyway so I'm calling this good enough. + /// + /// The actual number of struct and vec prototypes verified by this test is + /// astronomical, because any valid combinatorial sequence of paths made of + /// these unique values composes a unique stream. + /// + /// None of this of course speaks to actual collision probabilities for the + /// resulting sequence taking into account values on the stream that are not + /// SequenceNumber and a given hash function, except that the given + /// implementation of SequenceNumber should not itself contribute to a + /// collision + #[test] + fn no_collisions_for_common_prototypes_64() { + let mut collector = HashSet::new(); + let root = SequenceNumberInt::::root(); + collector.insert(root.rollup()); + recurse(root, 4, 50, &mut collector); + assert_eq!(30831, collector.len()); + } + + #[test] + fn no_collisions_for_common_prototypes_32() { + let mut collector = HashSet::new(); + let root = SequenceNumberInt::::root(); + collector.insert(root.rollup()); + recurse(root, 4, 50, &mut collector); + assert_eq!(30831, collector.len()); + } +} diff --git a/src/stable_hash.rs b/src/stable_hash.rs index b42d1ff..fbe0a59 100644 --- a/src/stable_hash.rs +++ b/src/stable_hash.rs @@ -1,14 +1,20 @@ -use {crate::prelude::*, std::ops::BitXorAssign}; +use crate::prelude::*; + +pub trait UnorderedAggregator { + fn write(&mut self, value: impl StableHash, sequence_number: T); +} /// Like Hasher, but consistent across: /// * builds (independent of rustc version or std implementation details) /// * platforms (eg: 32 bit & 64 bit, x68 and ARM) /// * processes (multiple runs of the same program) -/// -/// This is not a cryptographic strength digest. -pub trait StableHasher: Default { - type Out: BitXorAssign + StableHash + Default; - fn write(&mut self, sequence_number: impl SequenceNumber, bytes: &[u8]); +pub trait StableHasher { + type Out; + type Seq: SequenceNumber; + type Unordered: UnorderedAggregator; + fn write(&mut self, sequence_number: Self::Seq, bytes: &[u8]); + fn start_unordered(&mut self) -> Self::Unordered; + fn finish_unordered(&mut self, unordered: Self::Unordered, sequence_number: Self::Seq); fn finish(&self) -> Self::Out; } @@ -16,8 +22,6 @@ pub trait StableHasher: Default { /// * builds (independent of rustc version or std implementation details) /// * platforms (eg: 32 bit & 64 bit, x68 and ARM) /// * processes (multiple runs of the same program) -/// -/// This is not a cryptographic strength digest. pub trait StableHash { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher); + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H); } diff --git a/src/utils.rs b/src/utils.rs index 2d34e8a..6c3c162 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,9 @@ use crate::prelude::*; +use crate::sequence_number::UInt; use crate::SequenceNumberInt; use std::borrow::Borrow as _; use std::hash::Hasher; +use std::marker::PhantomData; /// Treat some &[u8] as a sequence of bytes, rather than a sequence of numbers. /// Using this can result in a significant performance gain but does not support @@ -9,7 +11,7 @@ use std::hash::Hasher; pub struct AsBytes<'a>(pub &'a [u8]); impl StableHash for AsBytes<'_> { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { if !self.0.is_empty() { state.write(sequence_number, self.0) } @@ -35,7 +37,7 @@ pub struct AsInt<'a> { } impl StableHash for AsInt<'_> { - fn stable_hash(&self, mut sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, mut sequence_number: H::Seq, state: &mut H) { self.is_negative .stable_hash(sequence_number.next_child(), state); let canon = trim_zeros(self.little_endian); @@ -45,29 +47,62 @@ impl StableHash for AsInt<'_> { } } -pub fn stable_hash(value: &V) -> T::Out { - let mut hasher = T::default(); - value.stable_hash(S::root(), &mut hasher); +pub fn stable_hash(value: &T) -> H::Out { + let mut hasher = H::default(); + value.stable_hash(H::Seq::root(), &mut hasher); hasher.finish() } pub fn stable_hash_with_hasher(value: &V) -> u64 { - stable_hash::, SequenceNumberInt, _>(value) + stable_hash::>, _>(value) } /// Wraps a Hasher to implement StableHasher. It must be known that the Hasher behaves in /// a consistent manner regardless of platform or process. #[derive(Default)] -pub struct StableHasherWrapper(T); +pub struct StableHasherWrapper { + hasher: H, + _marker: PhantomData<*const Seq>, +} + +pub struct XorAggregator { + value: u64, + _marker: PhantomData<*const T>, +} -impl StableHasher for StableHasherWrapper { +impl crate::stable_hash::UnorderedAggregator> + for XorAggregator>> +{ + fn write(&mut self, value: impl StableHash, sequence_number: SequenceNumberInt) { + let mut hasher: StableHasherWrapper> = Default::default(); + value.stable_hash(sequence_number, &mut hasher); + self.value ^= hasher.finish(); + } +} + +impl StableHasher for StableHasherWrapper> { type Out = u64; - fn write(&mut self, sequence_number: impl SequenceNumber, bytes: &[u8]) { - let seq_no = sequence_number.rollup(); - self.0.write(seq_no.borrow()); - self.0.write(bytes); + type Seq = SequenceNumberInt; + type Unordered = XorAggregator; + fn start_unordered(&mut self) -> Self::Unordered { + XorAggregator { + value: 0, + _marker: PhantomData, + } + } + fn finish_unordered( + &mut self, + unordered: Self::Unordered, + sequence_number: SequenceNumberInt, + ) { + unordered.value.stable_hash(sequence_number, self); + } + fn write(&mut self, sequence_number: Self::Seq, bytes: &[u8]) { + let seq_no = sequence_number.rollup().to_le_bytes(); + self.hasher.write(seq_no.borrow()); + self.hasher.write(bytes); } fn finish(&self) -> Self::Out { - self.0.finish() + self.hasher.finish() } } diff --git a/tests/backward_compatibility.rs b/tests/backward_compatibility.rs index 3eff3dc..1d44a9a 100644 --- a/tests/backward_compatibility.rs +++ b/tests/backward_compatibility.rs @@ -1,15 +1,17 @@ +use stable_hash::crypto::SetHasher; use stable_hash::prelude::*; use stable_hash::utils::*; use std::hash::Hasher as _; use twox_hash::XxHash64; mod common; +use rustc_hex::ToHex; struct One { one: T0, } impl StableHash for One { - fn stable_hash(&self, mut sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, mut sequence_number: H::Seq, state: &mut H) { self.one.stable_hash(sequence_number.next_child(), state); } } @@ -20,7 +22,7 @@ struct Two { } impl StableHash for Two { - fn stable_hash(&self, mut sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, mut sequence_number: H::Seq, state: &mut H) { self.one.stable_hash(sequence_number.next_child(), state); self.two.stable_hash(sequence_number.next_child(), state); } @@ -33,7 +35,7 @@ fn add_optional_field() { one: 5u32, two: Option::::None, }; - equal!(7505743411322483516; one, two); + equal!(7505743411322483516, "3428a4134bfdac56aa04614504705b0ffd1d48f27777b109a793e5a641324212"; one, two); } #[test] @@ -43,7 +45,7 @@ fn add_default_field() { one: "one", two: "", }; - equal!(10092156604856295746; one, two); + equal!(10092156604856295746, "65bf96c193b5d365191b86da83097939ccd67ac226d9f3a3c991719e338de7ed"; one, two); } #[test] @@ -60,30 +62,27 @@ fn add_non_default_field() { fn next_child_calls_do_not_affect_output() { struct S0; impl StableHash for S0 { - fn stable_hash(&self, sequence_number: impl SequenceNumber, state: &mut impl StableHasher) { + fn stable_hash(&self, sequence_number: H::Seq, state: &mut H) { 1u32.stable_hash(sequence_number, state); } } struct S1; impl StableHash for S1 { - fn stable_hash( - &self, - mut sequence_number: impl SequenceNumber, - state: &mut impl StableHasher, - ) { + fn stable_hash(&self, mut sequence_number: H::Seq, state: &mut H) { 0u32.stable_hash(sequence_number.next_child(), state); 1u32.stable_hash(sequence_number, state); } } - equal!(4850997937794257732; S0, S1); + equal!(4850997937794257732, "044100289e98a89ed394a64fec6960dbab147ca5b6560883c9ce5d65cd69bf51"; S0, S1); } #[test] fn defaults_are_non_emitting() { - let empty = XxHash64::default().finish(); - equal!(empty; 0u32, false, Option::::None, 0i32, Vec::::new(), ""); + let empty_1 = XxHash64::default().finish(); + let empty_2: String = SetHasher::default().finish().to_hex(); + equal!(empty_1, &empty_2; false, Option::::None, 0i32, Vec::::new(), ""); } #[test] @@ -103,7 +102,7 @@ fn empty_vec_is_default() { one: true, two: Vec::::new(), }; - equal!(13575479216228042845; one, two); + equal!(13575479216228042845, "db4657c873e33a60e581eb5458aba6c76f510e023872c76a3134608619342c59"; one, two); } #[test] @@ -124,13 +123,16 @@ fn omitted_defaults_dont_collide() { #[test] fn as_bytes() { let v = vec![0u8]; - not_equal!(&v[..], AsBytes(&v[..])) + not_equal!(&v[..], AsBytes(&v[..])); + + let v = vec![1u8, 2u8]; + not_equal!(&v[..], AsBytes(&v[..])); } #[test] fn numbers_through_vec() { equal!( - 16256940196889123120; + 16256940196889123120, "25dfaa9f92a3f2b05a1bdfbc66ec594c545dc39ebdb0e9ae769350ea1726e2b7"; vec![1u32, 2u32], vec![1u16, 2u16] ); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index e7a9d92..02a21e2 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,5 @@ +use rustc_hex::ToHex; +use stable_hash::crypto::SetHasher; use stable_hash::*; use twox_hash::XxHash64; @@ -5,11 +7,17 @@ pub fn xxhash(value: &impl StableHash) -> u64 { utils::stable_hash_with_hasher::(value) } +pub fn crypto_hash(value: &impl StableHash) -> String { + let raw = utils::stable_hash::(value); + raw.to_hex() +} + #[macro_export] macro_rules! equal { - ($value:expr; $($data:expr),+) => { + ($value_xx:expr, $value_crypto:expr; $($data:expr),+) => { $( - assert_eq!(common::xxhash(&$data), $value); + assert_eq!(common::xxhash(&$data), $value_xx); + assert_eq!(&common::crypto_hash(&$data), $value_crypto); )+ } } @@ -17,6 +25,9 @@ macro_rules! equal { #[macro_export] macro_rules! not_equal { ($left:expr, $right:expr) => { - assert_ne!(common::xxhash(&$left), common::xxhash(&$right)); + assert!( + common::xxhash(&$left) != common::xxhash(&$right) + && common::crypto_hash(&$left) != common::crypto_hash(&$right) + ); }; } diff --git a/tests/number_lowerings.rs b/tests/number_lowerings.rs index 1bed51a..ea77407 100644 --- a/tests/number_lowerings.rs +++ b/tests/number_lowerings.rs @@ -1,65 +1,64 @@ #[macro_use] mod common; - use std::mem::size_of; macro_rules! nums_equal { - ($value:expr, $result:expr; $min:ty, $($t:ty),+) => { - assert_eq!(common::xxhash(&($value as $min)), $result); + ($value:expr, $result_xx:expr, $result_crypto:expr; $min:ty, $($t:ty),+) => { + equal!($result_xx, $result_crypto; ($value as $min)); $( - assert_eq!(common::xxhash(&($value as $t)), $result); + equal!($result_xx, $result_crypto; ($value as $t)); )+ if size_of::<$min>() <= size_of::() && $value >= 0 { - assert_eq!(common::xxhash(&($value as usize)), $result); + equal!($result_xx, $result_crypto; ($value as usize)); } if size_of::<$min>() <= size_of::() { - assert_eq!(common::xxhash(&($value as isize)), $result); + equal!($result_xx, $result_crypto; ($value as isize)); } } } #[test] fn up_to_u8() { - nums_equal!(9, 15695816615077189814; u8, i8, u16, i16, u32, i32, u64, i64, u128, i128); + nums_equal!(9, 15695816615077189814, "173097115007a0965e818effe3bc946da648604343807e529b1999b39a3a1e0b"; u8, i8, u16, i16, u32, i32, u64, i64, u128, i128); } #[test] fn up_to_u16() { - nums_equal!(22768, 14145019129129205421; u16, i16, u32, i32, u64, i64, u128, i128); + nums_equal!(22768, 14145019129129205421, "83b02b2e018f5e08a6d20f8e0f9fec918f1a091483673436c4f2a9cada41cb22"; u16, i16, u32, i32, u64, i64, u128, i128); } #[test] fn up_to_u32() { - nums_equal!(1147483648, 11536801980706475161; u32, i32, u64, i64, u128, i128); + nums_equal!(1147483648, 11536801980706475161, "c33297c44fb3f4a51d7b8e4bb619414e46a67419f3e8bc4e34b8b80f27db033a"; u32, i32, u64, i64, u128, i128); } #[test] fn up_to_u64() { - nums_equal!(8223372036854775808u64, 16219818938521503862; u64, i64, u128, i128); + nums_equal!(8223372036854775808u64, 16219818938521503862, "a7e31391c320def7aa0e5732034c0942ca6950adca6624fbaf2caee1147ceae2"; u64, i64, u128, i128); } #[test] fn up_to_u128() { - nums_equal!(160141183460469231731687303715884105728u128, 13892203652687889343; u128, i128); + nums_equal!(160141183460469231731687303715884105728u128, 13892203652687889343, "1c22c3e5312542ca82d3b63e21f2608864ac5a377d55f7c24d2c12eae2cd743e"; u128, i128); } #[test] fn down_to_i64() { - nums_equal!(-9223372036854775808i64, 6256200190077353066; i64, i128); + nums_equal!(-9223372036854775808i64, 6256200190077353066, "8aaff1a84ed29f58b02ee2d09605bfddfb8c3003bf7bb2d0c71265ceaa3457c2"; i64, i128); } #[test] fn down_to_i32() { - nums_equal!(-2147483647i32, 16152790417736434501; i32, i64, i128); + nums_equal!(-2147483647i32, 16152790417736434501, "473b346a411b0dd9066dbd81959418d22127c467670abc734450db67851ff004"; i32, i64, i128); } #[test] fn down_to_i16() { - nums_equal!(-12768i16, 16986113607939961363; i16, i32, i64, i128); + nums_equal!(-12768i16, 16986113607939961363, "f7d1323c9e76079022c5a120e4abd30044c2755cc96e467221a96320920daaea"; i16, i32, i64, i128); } #[test] fn down_to_i8() { - nums_equal!(-12i8, 3386756839162099456; i8, i16, i32, i64, i128); + nums_equal!(-12i8, 3386756839162099456, "867b0b908a1ee3f4b1473febd9a76e8950692e631b1c4e39b4c18d26606cba40"; i8, i16, i32, i64, i128); } diff --git a/tests/sequence_number_collisions.rs b/tests/sequence_number_collisions.rs deleted file mode 100644 index a875fdc..0000000 --- a/tests/sequence_number_collisions.rs +++ /dev/null @@ -1,64 +0,0 @@ -use stable_hash::{SequenceNumber, SequenceNumberInt}; - -use std::borrow::Borrow; -use std::collections::HashSet; -use std::hash::Hash; - -fn recurse>( - mut sequence_number: impl SequenceNumber, - depth: usize, - length: usize, - collector: &mut HashSet, -) { - // Struct/Recursion check - for _ in 0..6 { - let child = sequence_number.next_child(); - assert!(collector.insert(child.rollup())); - if depth != 0 { - recurse(child, depth - 1, length, collector); - } - } - // Vec check (not recursive) - // Tests larger vecs closer to the root, where larger vecs are more likely - for _ in 0..(length * depth * depth) { - let child = sequence_number.next_child(); - assert!(collector.insert(child.rollup())); - } -} - -/// This test demonstrates that our choice of primes and algorithm is a good -/// one for our use case of common structures to be digested by trying every -/// permutation of all structs several deep and long Vecs for children and -/// asserting 0 collisions on over 11 million common SequenceNumber:: -/// paths and almost 3.4 million SequenceNumber:: paths. Just for kicks I -/// ran it on over 700 million paths before committing, but this test did -/// not complete in a reasonable enough amount of time to be committed. -/// Larger than that and we get dangerously close to birthday collisions -/// anyway so I'm calling this good enough. -/// -/// The actual number of struct and vec prototypes verified by this test is -/// astronomical, because any valid combinatorial sequence of paths made of -/// these unique values composes a unique stream. -/// -/// None of this of course speaks to actual collision probabilities for the -/// resulting sequence taking into account values on the stream that are not -/// SequenceNumber and a given hash function, except that the given -/// implementation of SequenceNumber should not itself contribute to a -/// collision -#[test] -fn no_collisions_for_common_prototypes_64() { - let mut collector = HashSet::new(); - let root = SequenceNumberInt::::root(); - collector.insert(root.rollup()); - recurse(root, 4, 50, &mut collector); - assert_eq!(30831, collector.len()); -} - -#[test] -fn no_collisions_for_common_prototypes_32() { - let mut collector = HashSet::new(); - let root = SequenceNumberInt::::root(); - collector.insert(root.rollup()); - recurse(root, 4, 50, &mut collector); - assert_eq!(30831, collector.len()); -} diff --git a/tests/unordered_collections.rs b/tests/unordered_collections.rs index a7ce859..1e743e1 100644 --- a/tests/unordered_collections.rs +++ b/tests/unordered_collections.rs @@ -29,7 +29,7 @@ macro_rules! set( #[test] fn hash_map_eq() { equal!( - 5555886761104914033; + 13674932384445014398, "d4a9efb21dceee110e3fd411be17dc746d9bea6964c38837249b34b18aeddbc3"; map!{ 1 => "one", 2 => "two", 3 => "three" }, map!{ 3 => "three", 1 => "one", 2 => "two" } ); @@ -62,7 +62,7 @@ fn hash_map_ne_value() { #[test] fn hash_set_eq() { equal!( - 17027054512634741606; + 9210742648026089892, "8a04398787dba3aa0969241e5a63af434173ec7ee3048f80479cc321fbc7d194"; set!{1, 2, 3}, set!{3, 2, 1} );