Skip to content

Commit fc0302c

Browse files
committed
WIP: update hasher design
1 parent bdf1d83 commit fc0302c

File tree

3 files changed

+53
-77
lines changed

3 files changed

+53
-77
lines changed

src/hashing.rs

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
//!
2929
//! [hmac]: https://en.wikipedia.org/wiki/HMAC#Design_principles "HMAC: Design principles"
3030
31-
use alloc::string::String;
3231
use core::{marker::PhantomData, ops::Deref};
3332

3433
use blake2::{Blake2b, Blake2bVar};
@@ -49,6 +48,26 @@ use crate::{
4948
keys::SecretKey,
5049
};
5150

51+
/// An enumeration specifying flags used by hashers.
52+
/// Flags prepend anything included as hasher input.
53+
///
54+
/// The idea is that when putting input into a hasher, we include several things:
55+
/// - A flag indicating the input type, encoded as a byte
56+
/// - If the input is of variable length (determined by the type), a little-endian 64-bit encoding of the length
57+
/// - The input, encoded as bytes
58+
/// By doing so, we mitigate the risk of collision.
59+
#[repr(u8)]
60+
enum Flag {
61+
/// An initial domain separator indicating the purpose of the hasher
62+
DomainSeparator = 0,
63+
/// The version of the hasher, which MUST be a single byte
64+
Version,
65+
/// A label that can be used to differentiate uses of the hasher
66+
Label,
67+
/// Arbitrary byte data to be added to the hasher
68+
Data,
69+
}
70+
5271
/// The `DomainSeparation` trait is used to inject domain separation tags into the [`DomainSeparatedHasher`] in a
5372
/// way that can be applied consistently, but without hard-coding anything into the hasher itself.
5473
///
@@ -65,64 +84,27 @@ pub trait DomainSeparation {
6584
/// Returns the category label for the metadata tag. For example, `tari_hmac`
6685
fn domain() -> &'static str;
6786

68-
/// The domain separation tag is defined as `{domain}.v{version}.{label}`, where the version and tag are
69-
/// typically hard-coded into the implementing type, and the label is provided per specific application of the
70-
/// domain
71-
fn domain_separation_tag<S: AsRef<str>>(label: S) -> String {
72-
if !label.as_ref().is_empty() {
73-
return format!("{}.v{}.{}", Self::domain(), Self::version(), label.as_ref());
74-
}
75-
format!("{}.v{}", Self::domain(), Self::version())
76-
}
77-
78-
/// Adds the domain separation tag to the given digest. The domain separation tag is defined as
79-
/// `{domain}.v{version}.{label}`, where the version and tag are typically hard-coded into the implementing
80-
/// type, and the label is provided per specific application of the domain.
87+
/// Performs complete domain separation by including a domain separator, version, and label.
8188
fn add_domain_separation_tag<S: AsRef<[u8]>, D: Digest>(digest: &mut D, label: S) {
82-
let label = if label.as_ref().is_empty() { &[] } else { label.as_ref() };
83-
let domain = Self::domain();
84-
let (version_offset, version) = byte_to_decimal_ascii_bytes(Self::version());
85-
let len = if label.is_empty() {
86-
// 2 additional bytes are 1 x '.' delimiters and 'v' tag for version
87-
domain.len() + (3 - version_offset) + 2
88-
} else {
89-
// 3 additional bytes are 2 x '.' delimiters and 'v' tag for version
90-
domain.len() + (3 - version_offset) + label.len() + 3
91-
};
92-
let len = (len as u64).to_le_bytes();
93-
digest.update(len);
94-
digest.update(domain);
95-
digest.update(b".v");
96-
digest.update(&version[version_offset..]);
97-
if !label.is_empty() {
98-
digest.update(b".");
99-
digest.update(label);
100-
}
89+
// Domain separator
90+
let domain_bytes = Self::domain().as_bytes();
91+
let domain_length = domain_bytes.len() as u64;
92+
digest.update([Flag::DomainSeparator as u8]);
93+
digest.update(domain_length.to_le_bytes());
94+
digest.update(domain_bytes);
95+
96+
// Version; this is of fixed length, so we don't need to use length prepending
97+
digest.update([Flag::Version as u8]);
98+
digest.update([Self::version()]);
99+
100+
// Label
101+
let label_length = label.as_ref().len() as u64;
102+
digest.update([Flag::Label as u8]);
103+
digest.update(label_length.to_le_bytes());
104+
digest.update(label);
101105
}
102106
}
103107

104-
/// Converts a byte value to ASCII bytes that represent its value in big-endian order. This function returns a tuple
105-
/// containing the inclusive index of the most significant decimal value byte, and the 3 ASCII bytes (big-endian). For
106-
/// example, byte_to_decimal_ascii_bytes(0) returns (2, [0, 0, 48]).
107-
/// byte_to_decimal_ascii_bytes(42) returns (1, [0, 52, 50]).
108-
/// byte_to_decimal_ascii_bytes(255) returns (0, [50, 53, 53]).
109-
fn byte_to_decimal_ascii_bytes(mut byte: u8) -> (usize, [u8; 3]) {
110-
const ZERO_ASCII_CHAR: u8 = 48;
111-
// A u8 can only ever be a 3 char number.
112-
let mut bytes = [0u8, 0u8, ZERO_ASCII_CHAR];
113-
let mut pos = 3usize;
114-
if byte == 0 {
115-
return (2, bytes);
116-
}
117-
while byte > 0 {
118-
let rem = byte % 10;
119-
byte /= 10;
120-
bytes[pos - 1] = ZERO_ASCII_CHAR + rem;
121-
pos -= 1;
122-
}
123-
(pos, bytes)
124-
}
125-
126108
//-------------------------------------- Domain Separated Hash ---------------------------------------------------
127109

128110
/// A hash value, guaranteed, as far as possible, to have been created using a hash function that has been randomly and
@@ -271,11 +253,12 @@ impl<D: Digest, M: DomainSeparation> DomainSeparatedHasher<D, M> {
271253
}
272254
}
273255

274-
/// Adds the data to the digest function by first appending the length of the data in the byte array, and then
275-
/// supplying the data itself.
256+
/// Adds the data to the digest function.
257+
/// This is done safely in a manner that prevents collisions.
276258
pub fn update(&mut self, data: impl AsRef<[u8]>) {
277-
let len = (data.as_ref().len() as u64).to_le_bytes();
278-
self.inner.update(len);
259+
let data_length = (data.as_ref().len() as u64).to_le_bytes();
260+
self.inner.update([Flag::Data as u8]);
261+
self.inner.update(data_length);
279262
self.inner.update(data);
280263
}
281264

@@ -658,7 +641,6 @@ mod test {
658641
use tari_utilities::hex::{from_hex, to_hex};
659642

660643
use crate::hashing::{
661-
byte_to_decimal_ascii_bytes,
662644
AsFixedBytes,
663645
DomainSeparatedHasher,
664646
DomainSeparation,
@@ -711,8 +693,6 @@ mod test {
711693
fn mac_domain_metadata() {
712694
assert_eq!(MacDomain::version(), 1);
713695
assert_eq!(MacDomain::domain(), "com.tari.mac");
714-
assert_eq!(MacDomain::domain_separation_tag(""), "com.tari.mac.v1");
715-
assert_eq!(MacDomain::domain_separation_tag("test"), "com.tari.mac.v1.test");
716696
}
717697

718698
#[test]
@@ -752,7 +732,8 @@ mod test {
752732
#[test]
753733
fn dst_hasher() {
754734
hash_domain!(GenericHashDomain, "com.tari.generic");
755-
assert_eq!(GenericHashDomain::domain_separation_tag(""), "com.tari.generic.v1");
735+
assert_eq!(GenericHashDomain::domain(), "com.tari.generic");
736+
assert_eq!(GenericHashDomain::version(), 1);
756737
let hash = DomainSeparatedHasher::<Blake2b<U32>, GenericHashDomain>::new_with_label("test_hasher")
757738
.chain("some foo")
758739
.finalize();
@@ -776,7 +757,8 @@ mod test {
776757
#[test]
777758
fn digest_is_the_same_as_standard_api() {
778759
hash_domain!(MyDemoHasher, "com.macro.test");
779-
assert_eq!(MyDemoHasher::domain_separation_tag(""), "com.macro.test.v1");
760+
assert_eq!(MyDemoHasher::domain(), "com.macro.test");
761+
assert_eq!(MyDemoHasher::version(), 1);
780762
util::hash_test::<DomainSeparatedHasher<Blake2b<U32>, MyDemoHasher>>(
781763
&[0, 0, 0],
782764
"d4cbf5b6b97485a991973db8a6ce4d3fc660db5dff5f55f2b0cb363fca34b0a2",
@@ -803,21 +785,24 @@ mod test {
803785
#[test]
804786
fn can_be_used_as_digest() {
805787
hash_domain!(MyDemoHasher, "com.macro.test");
806-
assert_eq!(MyDemoHasher::domain_separation_tag(""), "com.macro.test.v1");
788+
assert_eq!(MyDemoHasher::domain(), "com.macro.test");
789+
assert_eq!(MyDemoHasher::version(), 1);
807790
util::hash_test::<DomainSeparatedHasher<Blake2b<U32>, MyDemoHasher>>(
808791
&[0, 0, 0],
809792
"d4cbf5b6b97485a991973db8a6ce4d3fc660db5dff5f55f2b0cb363fca34b0a2",
810793
);
811794

812795
hash_domain!(MyDemoHasher2, "com.macro.test", 2);
813-
assert_eq!(MyDemoHasher2::domain_separation_tag(""), "com.macro.test.v2");
796+
assert_eq!(MyDemoHasher2::domain(), "com.macro.test");
797+
assert_eq!(MyDemoHasher2::version(), 2);
814798
util::hash_test::<DomainSeparatedHasher<Blake2b<U32>, MyDemoHasher2>>(
815799
&[0, 0, 0],
816800
"ce327b02271d035bad4dcc1e69bc292392ee4ee497f1f8467d54bf4b4c72639a",
817801
);
818802

819803
hash_domain!(TariHasher, "com.tari.hasher");
820-
assert_eq!(TariHasher::domain_separation_tag(""), "com.tari.hasher.v1");
804+
assert_eq!(TariHasher::domain(), "com.tari.hasher");
805+
assert_eq!(TariHasher::version(), 1);
821806
util::hash_test::<DomainSeparatedHasher<Blake2b<U32>, TariHasher>>(
822807
&[0, 0, 0],
823808
"ae359f05bb76c646c6767d25f53893fc38b0c7b56f8a74a1cbb008ea3ffc183f",
@@ -871,8 +856,6 @@ mod test {
871856
"com.discworld"
872857
}
873858
}
874-
let domain = "com.discworld.v42.turtles";
875-
assert_eq!(MyDemoHasher::domain_separation_tag("turtles"), domain);
876859
let hash = DomainSeparatedHasher::<Blake2b<U32>, MyDemoHasher>::new_with_label("turtles").finalize();
877860
let expected = Blake2b::<U32>::default()
878861
.chain((domain.len() as u64).to_le_bytes())

src/ristretto/ristretto_keys.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -986,10 +986,6 @@ mod test {
986986
fn ristretto_kdf_metadata() {
987987
assert_eq!(RistrettoKdf::version(), 1);
988988
assert_eq!(RistrettoKdf::domain(), "com.tari.kdf.ristretto");
989-
assert_eq!(
990-
RistrettoKdf::domain_separation_tag("test"),
991-
"com.tari.kdf.ristretto.v1.test"
992-
);
993989
}
994990

995991
#[test]

src/signatures/schnorr.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,6 @@ mod test {
362362
#[test]
363363
fn schnorr_hash_domain() {
364364
assert_eq!(SchnorrSigChallenge::domain(), "com.tari.schnorr_signature");
365-
assert_eq!(
366-
SchnorrSigChallenge::domain_separation_tag("test"),
367-
"com.tari.schnorr_signature.v1.test"
368-
);
365+
assert_eq!(SchnorrSigChallenge::version(), 1);
369366
}
370367
}

0 commit comments

Comments
 (0)