@@ -31,22 +31,73 @@ pub fn verdict(op: u32, bound: u64, value: u64) -> bool {
3131 }
3232}
3333
34- fn rotr (x : u32 , r : u32 ) -> u32 {
35- (x >> r ) | (x << (32 - r ))
34+ unconstrained fn split (x : u32 , r : u32 ) -> ( Field , Field ) {
35+ (( x & (( 1 << r ) - 1 )) as Field , (( x >> r ) | (x << (32 - r ))) as Field )
3636}
3737
38- fn g (s : &mut [u32 ; 16 ], a : u32 , b : u32 , c : u32 , d : u32 , x : u32 , y : u32 ) {
38+ /// x >>> R as the hand-written ACIR does it: x = rot * 2^R - lo * (2^32 - 1)
39+ /// with lo < 2^R and rot < 2^32. Both bounds are explicit; the compiler drops
40+ /// the 32-bit check where an XOR on the result already implies it.
41+ fn rotr <let R : u32 >(x : u32 ) -> u32 {
42+ // Safety: lo and rot are constrained below.
43+ let (lo , rot ) = unsafe { split (x , R ) };
44+ lo .assert_max_bit_size ::<R >();
45+ rot .assert_max_bit_size ::<32 >();
46+ let p : Field = (1 << R ) as Field ;
47+ assert_eq (x as Field , rot * p - lo * 4294967295 );
48+ rot as u32
49+ }
50+
51+ unconstrained fn carry_split (s : Field ) -> (Field , Field ) {
52+ let v = s as u64 ;
53+ ((v & 0xFFFFFFFF ) as Field , (v >> 32 ) as Field )
54+ }
55+
56+ /// (sum of 32-bit terms) mod 2^32 with an explicit carry of at most 2 bits.
57+ fn add (s : Field ) -> u32 {
58+ // Safety: out and the carry are range-checked below.
59+ let (out , carry ) = unsafe { carry_split (s ) };
60+ out .assert_max_bit_size ::<32 >();
61+ carry .assert_max_bit_size ::<2 >();
62+ assert_eq (s , out + carry * 4294967296 );
63+ out as u32
64+ }
65+
66+ /// The obvious Noir: u32 shifts, OR and wrapping_add.
67+ fn g_plain (s : &mut [u32 ; 16 ], a : u32 , b : u32 , c : u32 , d : u32 , x : u32 , y : u32 ) {
3968 s [a ] = s [a ].wrapping_add (s [b ]).wrapping_add (x );
40- s [d ] = rotr (s [d ] ^ s [a ], 16 );
69+ s [d ] = rotr_plain (s [d ] ^ s [a ], 16 );
4170 s [c ] = s [c ].wrapping_add (s [d ]);
42- s [b ] = rotr (s [b ] ^ s [c ], 12 );
71+ s [b ] = rotr_plain (s [b ] ^ s [c ], 12 );
4372 s [a ] = s [a ].wrapping_add (s [b ]).wrapping_add (y );
44- s [d ] = rotr (s [d ] ^ s [a ], 8 );
73+ s [d ] = rotr_plain (s [d ] ^ s [a ], 8 );
4574 s [c ] = s [c ].wrapping_add (s [d ]);
46- s [b ] = rotr (s [b ] ^ s [c ], 7 );
75+ s [b ] = rotr_plain (s [b ] ^ s [c ], 7 );
76+ }
77+
78+ fn rotr_plain (x : u32 , r : u32 ) -> u32 {
79+ (x >> r ) | (x << (32 - r ))
4780}
4881
49- fn compress (cv : [u32 ; 8 ], block : [u32 ; 16 ], block_len : u32 , flags : u32 ) -> [u32 ; 8 ] {
82+ /// The same G with the rotations and additions written as field
83+ /// constraints, the way blake3_leaf.py emits them.
84+ fn g (s : &mut [u32 ; 16 ], a : u32 , b : u32 , c : u32 , d : u32 , x : u32 , y : u32 ) {
85+ s [a ] = add (s [a ] as Field + s [b ] as Field + x as Field );
86+ s [d ] = rotr ::<16 >(s [d ] ^ s [a ]);
87+ s [c ] = add (s [c ] as Field + s [d ] as Field );
88+ s [b ] = rotr ::<12 >(s [b ] ^ s [c ]);
89+ s [a ] = add (s [a ] as Field + s [b ] as Field + y as Field );
90+ s [d ] = rotr ::<8 >(s [d ] ^ s [a ]);
91+ s [c ] = add (s [c ] as Field + s [d ] as Field );
92+ s [b ] = rotr ::<7 >(s [b ] ^ s [c ]);
93+ }
94+
95+ fn compress <let TUNED : u32 >(
96+ cv : [u32 ; 8 ],
97+ block : [u32 ; 16 ],
98+ block_len : u32 ,
99+ flags : u32 ,
100+ ) -> [u32 ; 8 ] {
50101 let mut s : [u32 ; 16 ] = [0 ; 16 ];
51102 for i in 0 ..8 {
52103 s [i ] = cv [i ];
@@ -58,14 +109,46 @@ fn compress(cv: [u32; 8], block: [u32; 16], block_len: u32, flags: u32) -> [u32;
58109 s [15 ] = flags ;
59110 let mut m = block ;
60111 for _ in 0 ..7 {
61- g (&mut s , 0 , 4 , 8 , 12 , m [0 ], m [1 ]);
62- g (&mut s , 1 , 5 , 9 , 13 , m [2 ], m [3 ]);
63- g (&mut s , 2 , 6 , 10 , 14 , m [4 ], m [5 ]);
64- g (&mut s , 3 , 7 , 11 , 15 , m [6 ], m [7 ]);
65- g (&mut s , 0 , 5 , 10 , 15 , m [8 ], m [9 ]);
66- g (&mut s , 1 , 6 , 11 , 12 , m [10 ], m [11 ]);
67- g (&mut s , 2 , 7 , 8 , 13 , m [12 ], m [13 ]);
68- g (&mut s , 3 , 4 , 9 , 14 , m [14 ], m [15 ]);
112+ if TUNED == 1 {
113+ g (&mut s , 0 , 4 , 8 , 12 , m [0 ], m [1 ]);
114+ } else {
115+ g_plain (&mut s , 0 , 4 , 8 , 12 , m [0 ], m [1 ]);
116+ }
117+ if TUNED == 1 {
118+ g (&mut s , 1 , 5 , 9 , 13 , m [2 ], m [3 ]);
119+ } else {
120+ g_plain (&mut s , 1 , 5 , 9 , 13 , m [2 ], m [3 ]);
121+ }
122+ if TUNED == 1 {
123+ g (&mut s , 2 , 6 , 10 , 14 , m [4 ], m [5 ]);
124+ } else {
125+ g_plain (&mut s , 2 , 6 , 10 , 14 , m [4 ], m [5 ]);
126+ }
127+ if TUNED == 1 {
128+ g (&mut s , 3 , 7 , 11 , 15 , m [6 ], m [7 ]);
129+ } else {
130+ g_plain (&mut s , 3 , 7 , 11 , 15 , m [6 ], m [7 ]);
131+ }
132+ if TUNED == 1 {
133+ g (&mut s , 0 , 5 , 10 , 15 , m [8 ], m [9 ]);
134+ } else {
135+ g_plain (&mut s , 0 , 5 , 10 , 15 , m [8 ], m [9 ]);
136+ }
137+ if TUNED == 1 {
138+ g (&mut s , 1 , 6 , 11 , 12 , m [10 ], m [11 ]);
139+ } else {
140+ g_plain (&mut s , 1 , 6 , 11 , 12 , m [10 ], m [11 ]);
141+ }
142+ if TUNED == 1 {
143+ g (&mut s , 2 , 7 , 8 , 13 , m [12 ], m [13 ]);
144+ } else {
145+ g_plain (&mut s , 2 , 7 , 8 , 13 , m [12 ], m [13 ]);
146+ }
147+ if TUNED == 1 {
148+ g (&mut s , 3 , 4 , 9 , 14 , m [14 ], m [15 ]);
149+ } else {
150+ g_plain (&mut s , 3 , 4 , 9 , 14 , m [14 ], m [15 ]);
151+ }
69152 let mut next = m ;
70153 for i in 0 ..16 {
71154 next [i ] = m [PERMUTATION [i ]];
@@ -90,8 +173,9 @@ unconstrained fn digit_count(value: u64) -> u32 {
90173}
91174
92175/// big-endian(blake3(token)[1..32]) for the canonical xsd:integer token of
93- /// value, with one variable-length hash over two compressions.
94- pub fn literal_hash (value : u64 ) -> Field {
176+ /// value, with one variable-length hash over two compressions. TUNED = 1
177+ /// writes the rotations and additions as field constraints; 0 uses u32 ops.
178+ pub fn literal_hash <let TUNED : u32 >(value : u64 ) -> Field {
95179 let mut digits : [u8 ; 20 ] = [0 ; 20 ];
96180 let mut remaining = value ;
97181 for i in 0 ..20 {
@@ -148,8 +232,8 @@ pub fn literal_hash(value: u64) -> Field {
148232 let two = n > 18 ;
149233 let len = n + 46 ;
150234 let (len1 , flags1 ) = if two { (64 , 1 ) } else { (len , 11 ) };
151- let out1 = compress (IV , first , len1 , flags1 );
152- let out2 = compress (out1 , second , if two { len - 64 } else { 0 }, 10 );
235+ let out1 = compress ::< TUNED > (IV , first , len1 , flags1 );
236+ let out2 = compress ::< TUNED > (out1 , second , if two { len - 64 } else { 0 }, 10 );
153237 let digest = if two { out2 } else { out1 };
154238 let mut hs : Field = 0 ;
155239 for i in 0 ..8 {
0 commit comments