28
28
//!
29
29
//! [hmac]: https://en.wikipedia.org/wiki/HMAC#Design_principles "HMAC: Design principles"
30
30
31
- use alloc:: string:: String ;
32
31
use core:: { marker:: PhantomData , ops:: Deref } ;
33
32
34
33
use blake2:: { Blake2b , Blake2bVar } ;
@@ -49,6 +48,26 @@ use crate::{
49
48
keys:: SecretKey ,
50
49
} ;
51
50
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
+
52
71
/// The `DomainSeparation` trait is used to inject domain separation tags into the [`DomainSeparatedHasher`] in a
53
72
/// way that can be applied consistently, but without hard-coding anything into the hasher itself.
54
73
///
@@ -65,64 +84,27 @@ pub trait DomainSeparation {
65
84
/// Returns the category label for the metadata tag. For example, `tari_hmac`
66
85
fn domain ( ) -> & ' static str ;
67
86
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.
81
88
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) ;
101
105
}
102
106
}
103
107
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
-
126
108
//-------------------------------------- Domain Separated Hash ---------------------------------------------------
127
109
128
110
/// 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> {
271
253
}
272
254
}
273
255
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 .
276
258
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) ;
279
262
self . inner . update ( data) ;
280
263
}
281
264
@@ -658,7 +641,6 @@ mod test {
658
641
use tari_utilities:: hex:: { from_hex, to_hex} ;
659
642
660
643
use crate :: hashing:: {
661
- byte_to_decimal_ascii_bytes,
662
644
AsFixedBytes ,
663
645
DomainSeparatedHasher ,
664
646
DomainSeparation ,
@@ -711,8 +693,6 @@ mod test {
711
693
fn mac_domain_metadata ( ) {
712
694
assert_eq ! ( MacDomain :: version( ) , 1 ) ;
713
695
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" ) ;
716
696
}
717
697
718
698
#[ test]
@@ -752,7 +732,8 @@ mod test {
752
732
#[ test]
753
733
fn dst_hasher ( ) {
754
734
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 ) ;
756
737
let hash = DomainSeparatedHasher :: < Blake2b < U32 > , GenericHashDomain > :: new_with_label ( "test_hasher" )
757
738
. chain ( "some foo" )
758
739
. finalize ( ) ;
@@ -776,7 +757,8 @@ mod test {
776
757
#[ test]
777
758
fn digest_is_the_same_as_standard_api ( ) {
778
759
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 ) ;
780
762
util:: hash_test :: < DomainSeparatedHasher < Blake2b < U32 > , MyDemoHasher > > (
781
763
& [ 0 , 0 , 0 ] ,
782
764
"d4cbf5b6b97485a991973db8a6ce4d3fc660db5dff5f55f2b0cb363fca34b0a2" ,
@@ -803,21 +785,24 @@ mod test {
803
785
#[ test]
804
786
fn can_be_used_as_digest ( ) {
805
787
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 ) ;
807
790
util:: hash_test :: < DomainSeparatedHasher < Blake2b < U32 > , MyDemoHasher > > (
808
791
& [ 0 , 0 , 0 ] ,
809
792
"d4cbf5b6b97485a991973db8a6ce4d3fc660db5dff5f55f2b0cb363fca34b0a2" ,
810
793
) ;
811
794
812
795
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 ) ;
814
798
util:: hash_test :: < DomainSeparatedHasher < Blake2b < U32 > , MyDemoHasher2 > > (
815
799
& [ 0 , 0 , 0 ] ,
816
800
"ce327b02271d035bad4dcc1e69bc292392ee4ee497f1f8467d54bf4b4c72639a" ,
817
801
) ;
818
802
819
803
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 ) ;
821
806
util:: hash_test :: < DomainSeparatedHasher < Blake2b < U32 > , TariHasher > > (
822
807
& [ 0 , 0 , 0 ] ,
823
808
"ae359f05bb76c646c6767d25f53893fc38b0c7b56f8a74a1cbb008ea3ffc183f" ,
@@ -871,8 +856,6 @@ mod test {
871
856
"com.discworld"
872
857
}
873
858
}
874
- let domain = "com.discworld.v42.turtles" ;
875
- assert_eq ! ( MyDemoHasher :: domain_separation_tag( "turtles" ) , domain) ;
876
859
let hash = DomainSeparatedHasher :: < Blake2b < U32 > , MyDemoHasher > :: new_with_label ( "turtles" ) . finalize ( ) ;
877
860
let expected = Blake2b :: < U32 > :: default ( )
878
861
. chain ( ( domain. len ( ) as u64 ) . to_le_bytes ( ) )
0 commit comments