8ca381a9c2
* cleanup our from_hex() * fix keychain tests * add test coverage for empty hex string
211 lines
6.3 KiB
Rust
211 lines
6.3 KiB
Rust
// Copyright 2020 The Grin Developers
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! short ids for compact blocks
|
|
|
|
use crate::core::hash::{DefaultHashable, Hash, Hashed};
|
|
use crate::ser::{self, Readable, Reader, Writeable, Writer};
|
|
use byteorder::{ByteOrder, LittleEndian};
|
|
use siphasher::sip::SipHasher24;
|
|
use std::cmp::{min, Ordering};
|
|
use util;
|
|
|
|
/// The size of a short id used to identify inputs|outputs|kernels (6 bytes)
|
|
pub const SHORT_ID_SIZE: usize = 6;
|
|
|
|
/// A trait for types that have a short_id (inputs/outputs/kernels)
|
|
pub trait ShortIdentifiable {
|
|
/// The short_id of a kernel uses a hash built from the block_header *and* a
|
|
/// connection specific nonce to minimize the effect of collisions.
|
|
fn short_id(&self, hash: &Hash, nonce: u64) -> ShortId;
|
|
}
|
|
|
|
impl<H: Hashed> ShortIdentifiable for H {
|
|
/// Generate a short_id via the following -
|
|
///
|
|
/// * extract k0/k1 from block_hash hashed with the nonce (first two u64
|
|
/// values) * initialize a siphasher24 with k0/k1
|
|
/// * self.hash() passing in the siphasher24 instance
|
|
/// * drop the 2 most significant bytes (to return a 6 byte short_id)
|
|
///
|
|
fn short_id(&self, hash: &Hash, nonce: u64) -> ShortId {
|
|
// take the block hash and the nonce and hash them together
|
|
let hash_with_nonce = (hash, nonce).hash();
|
|
|
|
// we "use" core::hash::Hash in the outer namespace
|
|
// so doing this here in the fn to minimize collateral damage/confusion
|
|
use std::hash::Hasher;
|
|
|
|
// extract k0/k1 from the block_hash
|
|
let k0 = LittleEndian::read_u64(&hash_with_nonce.as_bytes()[0..8]);
|
|
let k1 = LittleEndian::read_u64(&hash_with_nonce.as_bytes()[8..16]);
|
|
|
|
// initialize a siphasher24 with k0/k1
|
|
let mut sip_hasher = SipHasher24::new_with_keys(k0, k1);
|
|
|
|
// hash our id (self.hash()) using the siphasher24 instance
|
|
sip_hasher.write(&self.hash().to_vec()[..]);
|
|
let res = sip_hasher.finish();
|
|
|
|
// construct a short_id from the resulting bytes (dropping the 2 most
|
|
// significant bytes)
|
|
let mut buf = [0; 8];
|
|
LittleEndian::write_u64(&mut buf, res);
|
|
ShortId::from_bytes(&buf[0..6])
|
|
}
|
|
}
|
|
|
|
/// Short id for identifying inputs/outputs/kernels
|
|
#[derive(Clone, Serialize, Deserialize)]
|
|
pub struct ShortId([u8; 6]);
|
|
|
|
impl DefaultHashable for ShortId {}
|
|
// We want to sort short_ids in a canonical and consistent manner so we can
|
|
// verify sort order in the same way we do for full inputs|outputs|kernels
|
|
// themselves.
|
|
hashable_ord!(ShortId);
|
|
|
|
impl ::std::fmt::Debug for ShortId {
|
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
|
write!(f, "{}(", stringify!(ShortId))?;
|
|
write!(f, "{}", self.to_hex())?;
|
|
write!(f, ")")
|
|
}
|
|
}
|
|
|
|
impl Readable for ShortId {
|
|
fn read(reader: &mut dyn Reader) -> Result<ShortId, ser::Error> {
|
|
let v = reader.read_fixed_bytes(SHORT_ID_SIZE)?;
|
|
let mut a = [0; SHORT_ID_SIZE];
|
|
a.copy_from_slice(&v[..]);
|
|
Ok(ShortId(a))
|
|
}
|
|
}
|
|
|
|
impl Writeable for ShortId {
|
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
|
writer.write_fixed_bytes(&self.0)
|
|
}
|
|
}
|
|
|
|
impl ShortId {
|
|
/// Build a new short_id from a byte slice
|
|
pub fn from_bytes(bytes: &[u8]) -> ShortId {
|
|
let mut hash = [0; SHORT_ID_SIZE];
|
|
let copy_size = min(SHORT_ID_SIZE, bytes.len());
|
|
hash[..copy_size].copy_from_slice(&bytes[..copy_size]);
|
|
ShortId(hash)
|
|
}
|
|
|
|
/// Hex string representation of a short_id
|
|
pub fn to_hex(&self) -> String {
|
|
util::to_hex(self.0.to_vec())
|
|
}
|
|
|
|
/// Reconstructs a switch commit hash from a hex string.
|
|
pub fn from_hex(hex: &str) -> Result<ShortId, ser::Error> {
|
|
let bytes = util::from_hex(hex)
|
|
.map_err(|_| ser::Error::HexError("short_id from_hex error".to_string()))?;
|
|
Ok(ShortId::from_bytes(&bytes))
|
|
}
|
|
|
|
/// The zero short_id, convenient for generating a short_id for testing.
|
|
pub fn zero() -> ShortId {
|
|
ShortId::from_bytes(&[0])
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use crate::ser::{Writeable, Writer};
|
|
|
|
#[test]
|
|
fn short_id_ord() {
|
|
let id_1 = ShortId::from_bytes(&[1, 1, 1, 1]);
|
|
let id_2 = ShortId::from_bytes(&[2, 2, 2, 2]);
|
|
let id_3 = ShortId::from_bytes(&[3, 3, 3, 3]);
|
|
|
|
let mut ids = vec![id_1.clone(), id_2.clone(), id_3.clone()];
|
|
println!("{:?}", ids);
|
|
|
|
let mut hashes = ids.iter().map(|x| x.hash()).collect::<Vec<_>>();
|
|
println!("{:?}", hashes);
|
|
|
|
// NOTE: after sorting hash(3) comes before hash(2)
|
|
hashes.sort();
|
|
println!("{:?}", hashes);
|
|
assert_eq!(hashes, [id_1.hash(), id_3.hash(), id_2.hash()]);
|
|
|
|
// NOTE: this also applies to sorting the ids (we sort based on hashes)
|
|
ids.sort();
|
|
println!("{:?}", ids);
|
|
assert_eq!(ids, [id_1, id_3, id_2]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_short_id() {
|
|
// minimal struct for testing
|
|
// make it implement Writeable, therefore Hashable, therefore ShortIdentifiable
|
|
struct Foo(u64);
|
|
impl Writeable for Foo {
|
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
|
writer.write_u64(self.0)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl DefaultHashable for Foo {}
|
|
|
|
let foo = Foo(0);
|
|
|
|
let expected_hash =
|
|
Hash::from_hex("81e47a19e6b29b0a65b9591762ce5143ed30d0261e5d24a3201752506b20f15c")
|
|
.unwrap();
|
|
assert_eq!(foo.hash(), expected_hash);
|
|
|
|
let other_hash = Hash::default();
|
|
assert_eq!(
|
|
foo.short_id(&other_hash, foo.0),
|
|
ShortId::from_hex("4cc808b62476").unwrap()
|
|
);
|
|
|
|
let foo = Foo(5);
|
|
let expected_hash =
|
|
Hash::from_hex("3a42e66e46dd7633b57d1f921780a1ac715e6b93c19ee52ab714178eb3a9f673")
|
|
.unwrap();
|
|
assert_eq!(foo.hash(), expected_hash);
|
|
|
|
let other_hash = Hash::default();
|
|
assert_eq!(
|
|
foo.short_id(&other_hash, foo.0),
|
|
ShortId::from_hex("02955a094534").unwrap()
|
|
);
|
|
|
|
let foo = Foo(5);
|
|
let expected_hash =
|
|
Hash::from_hex("3a42e66e46dd7633b57d1f921780a1ac715e6b93c19ee52ab714178eb3a9f673")
|
|
.unwrap();
|
|
assert_eq!(foo.hash(), expected_hash);
|
|
|
|
let other_hash =
|
|
Hash::from_hex("81e47a19e6b29b0a65b9591762ce5143ed30d0261e5d24a3201752506b20f15c")
|
|
.unwrap();
|
|
assert_eq!(
|
|
foo.short_id(&other_hash, foo.0),
|
|
ShortId::from_hex("3e9cde72a687").unwrap()
|
|
);
|
|
}
|
|
}
|