1
- use super :: bytes32:: { hash_blobs, Bytes32 } ;
2
1
use super :: { ChildPos , PathBuilder } ;
3
2
use crate :: allocator:: { Allocator , NodePtr , SExp } ;
4
3
use crate :: serde:: serialized_length_atom;
5
4
use crate :: serde:: RandomState ;
6
5
use crate :: serde:: VisitedNodes ;
6
+ use rand:: prelude:: * ;
7
+ use sha1:: { Digest , Sha1 } ;
7
8
use std:: collections:: hash_map:: Entry ;
8
9
use std:: collections:: HashMap ;
9
10
10
11
const MIN_SERIALIZED_LENGTH : u64 = 4 ;
11
12
13
+ type Bytes20 = [ u8 ; 20 ] ;
14
+
15
+ fn hash_blobs ( salt : & [ u8 ] , blobs : & [ & [ u8 ] ] ) -> Bytes20 {
16
+ let mut ctx = Sha1 :: default ( ) ;
17
+ ctx. update ( salt) ;
18
+ for blob in blobs. iter ( ) {
19
+ ctx. update ( blob) ;
20
+ }
21
+ ctx. finalize ( ) . into ( )
22
+ }
23
+
12
24
#[ derive( Clone , Debug ) ]
13
25
struct NodeEntry {
14
26
/// the tree hash of this node. It may be None if it or any of its children
15
27
/// is the sentinel node, which means we can't compute the tree hash.
16
- tree_hash : Option < Bytes32 > ,
28
+ tree_hash : Option < Bytes20 > ,
17
29
/// a node can have an arbitrary number of parents, since they can be reused
18
30
/// this is a list of parent nodes, followed by whether we're the left or
19
31
/// right child. The u32 is an index into the node_entry vector.
@@ -75,7 +87,7 @@ pub struct TreeCache {
75
87
/// node_entry vector. For any given tree hash, we're only supposed to
76
88
/// have a single NodeEntry. There may be multiple NodePtr referring to
77
89
/// the same NodeEntry (if they are identical sub trees).
78
- hash_to_node : HashMap < Bytes32 , u32 , RandomState > ,
90
+ hash_to_node : HashMap < Bytes20 , u32 , RandomState > ,
79
91
80
92
/// When deserializing, we keep a stack of tokens we've parsed so far, this
81
93
/// stack is maintaining that same state, since that's what back-references
@@ -95,13 +107,20 @@ pub struct TreeCache {
95
107
/// update(), the tree is assumed to be placed at the sentinel node in the
96
108
/// previous call to update()
97
109
pub sentinel_node : Option < NodePtr > ,
110
+
111
+ /// We compute hash-trees using SHA-1 in order to determine whether the
112
+ /// trees are identical or not. To mitigate malicious SHA-1 hash collisions,
113
+ /// we stalt the hashes
114
+ salt : [ u8 ; 8 ] ,
98
115
}
99
116
100
117
impl TreeCache {
101
118
pub fn new ( sentinel : Option < NodePtr > ) -> Self {
119
+ let mut rng = rand:: thread_rng ( ) ;
102
120
Self {
103
121
sentinel_node : sentinel,
104
122
hash_to_node : HashMap :: with_hasher ( RandomState :: default ( ) ) ,
123
+ salt : rng. gen ( ) ,
105
124
..Default :: default ( )
106
125
}
107
126
}
@@ -200,7 +219,7 @@ impl TreeCache {
200
219
continue ;
201
220
}
202
221
let buf = a. atom ( node) ;
203
- let hash = hash_blobs ( & [ & [ 1 ] , buf. as_ref ( ) ] ) ;
222
+ let hash = hash_blobs ( & self . salt , & [ & [ 1 ] , buf. as_ref ( ) ] ) ;
204
223
205
224
// record the mapping of this node to the
206
225
// corresponding NodeEntry index
@@ -251,7 +270,11 @@ impl TreeCache {
251
270
let ( hash, idx) = if let ( Some ( left_hash) , Some ( right_hash) ) =
252
271
( left. tree_hash , right. tree_hash )
253
272
{
254
- let hash = hash_blobs ( & [ & [ 2 ] , left_hash. as_ref ( ) , right_hash. as_ref ( ) ] ) ;
273
+ let hash = hash_blobs (
274
+ & self . salt ,
275
+ & [ & [ 2 ] , left_hash. as_ref ( ) , right_hash. as_ref ( ) ] ,
276
+ ) ;
277
+
255
278
( Some ( hash) , self . hash_to_node . get ( & hash) )
256
279
} else {
257
280
( None , None )
0 commit comments