v2 / vlib / x / crypto / mldsa / encoding.v
594 lines · 547 sloc · 13.27 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1// Copyright 2025 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4//
5// Ported to V from Go's crypto/internal/fips140/mldsa.
6module mldsa
7
8import crypto.internal.subtle
9import crypto.sha3
10
11// algo. 22: pkEncode (s. 7.2)
12@[direct_array_access]
13fn pk_encode(rho []u8, t1 [][]u16, p Params) []u8 {
14 mut pk := rho.clone()
15 for i in 0 .. p.k {
16 w := t1[i]
17 mut j := 0
18 for j < n {
19 c0 := w[j]
20 c1 := w[j + 1]
21 c2 := w[j + 2]
22 c3 := w[j + 3]
23 pk << u8(c0)
24 pk << u8((c0 >> 8) | (c1 << 2))
25 pk << u8((c1 >> 6) | (c2 << 4))
26 pk << u8((c2 >> 4) | (c3 << 6))
27 pk << u8(c3 >> 2)
28 j += 4
29 }
30 }
31 return pk
32}
33
34// algo. 23: pkDecode (s. 7.2)
35@[direct_array_access]
36fn pk_decode(pk []u8, p Params) !([]u8, [][]u16) {
37 expected := pub_key_size(p)
38 if pk.len != expected {
39 return error('invalid public key length')
40 }
41 rho := pk[..32].clone()
42 // avoid cloning on each iteration
43 mut data := unsafe { pk[32..] }
44 mut t1 := [][]u16{len: p.k, init: []u16{len: n}}
45 for r in 0 .. p.k {
46 mut j := 0
47 for j < n {
48 b0 := data[0]
49 b1 := data[1]
50 b2 := data[2]
51 b3 := data[3]
52 b4 := data[4]
53 t1[r][j] = u16(b0) | (u16(b1 & 0x03) << 8)
54 t1[r][j + 1] = u16(b1 >> 2) | (u16(b2 & 0x0f) << 6)
55 t1[r][j + 2] = u16(b2 >> 4) | (u16(b3 & 0x3f) << 4)
56 t1[r][j + 3] = u16(b3 >> 6) | (u16(b4) << 2)
57 data = unsafe { data[5..] }
58 j += 4
59 }
60 }
61 return rho, t1
62}
63
64fn compute_pk_hash(pk []u8) [64]u8 {
65 return slice_to_64(sha3.shake256(pk, 64))
66}
67
68@[direct_array_access]
69fn compute_t1_hat(t1 [][]u16) []NttElement {
70 mut result := []NttElement{len: t1.len}
71 for i in 0 .. t1.len {
72 mut w := RingElement{}
73 for j in 0 .. n {
74 // panics if (t1[i][j] << d) >= q, c.f field.v
75 // only called from pk_decode, which produces 10bit vals (<= 1023)
76 // so 1023 << 13 = 8_380_416 = q - 1
77 z := field_to_montgomery(u32(t1[i][j]) << d) or { panic(err) }
78 w[j] = z
79 }
80 result[i] = ntt(w)
81 }
82 return result
83}
84
85// algo. 35: Power2Round (s. 7.4)
86fn power2_round(r FieldElement) (u16, FieldElement) {
87 rr_ := field_from_montgomery(r)
88 r1 := (rr_ + (1 << 12) - 1) >> d
89 r0 := field_sub_to_montgomery(rr_, r1 << d)
90 return u16(r1), r0
91}
92
93// algo. 37: HighBits (s. 7.4)
94@[direct_array_access]
95fn high_bits(r RingElement, p Params) [256]u8 {
96 mut w := [256]u8{}
97 match p.gamma2 {
98 32 {
99 for i in 0 .. n {
100 w[i] = high_bits_32(field_from_montgomery(r[i]))
101 }
102 }
103 88 {
104 for i in 0 .. n {
105 w[i] = high_bits_88(field_from_montgomery(r[i]))
106 }
107 }
108 else {
109 panic('mldsa: unsupported gamma2') // unreachable
110 }
111 }
112
113 return w
114}
115
116fn high_bits_32(x u32) u8 {
117 mut r1 := (x + 127) >> 7 // approx div by 2*gamma2
118 r1 = (r1 * 1025 + (1 << 21)) >> 22
119 r1 &= 0xf
120 return u8(r1)
121}
122
123fn high_bits_88(x u32) u8 {
124 mut r1 := (x + 127) >> 7 // approx div by 2*gamma2
125 r1 = (r1 * 11275 + (1 << 23)) >> 24
126 r1 = ct_select_eq(r1, 44, 0, r1)
127 return u8(r1)
128}
129
130// algo. 36: Decompose, gamma2 = (q-1)/32 (s. 7.4)
131fn decompose_32(r FieldElement) (u8, i32) {
132 x := field_from_montgomery(r)
133 r1 := high_bits_32(x)
134 r0 := i32(x) - i32(r1) * 2 * i32((q - 1) / 32)
135 r0_adj := ct_select_leq(i32(q / 2 + 1), r0, r0 - i32(q), r0)
136 return r1, r0_adj
137}
138
139// algo. 36: Decompose, gamma2 = (q-1)/88 (s. 7.4)
140fn decompose_88(r FieldElement) (u8, i32) {
141 x := field_from_montgomery(r)
142 r1 := high_bits_88(x)
143 r0 := i32(x) - i32(r1) * 2 * i32((q - 1) / 88)
144 r0_adj := ct_select_leq(i32(q / 2 + 1), r0, r0 - i32(q), r0)
145 return r1, r0_adj
146}
147
148@[direct_array_access]
149fn low_bits_exceed_bound(w RingElement, bound u32, p Params) bool {
150 match p.gamma2 {
151 32 {
152 for i in 0 .. n {
153 _, r0 := decompose_32(w[i])
154 if ct_abs(r0) >= bound {
155 return true
156 }
157 }
158 }
159 88 {
160 for i in 0 .. n {
161 _, r0 := decompose_88(w[i])
162 if ct_abs(r0) >= bound {
163 return true
164 }
165 }
166 }
167 else {
168 panic('mldsa: unsupported gamma2') // unreachable
169 }
170 }
171
172 return false
173}
174
175// algo. 40: UseHint (s. 7.4)
176@[direct_array_access]
177fn use_hint(r RingElement, h [256]u8, p Params) [256]u8 {
178 mut w := [256]u8{}
179 match p.gamma2 {
180 32 {
181 for i in 0 .. n {
182 w[i] = use_hint_32(r[i], h[i])
183 }
184 }
185 88 {
186 for i in 0 .. n {
187 w[i] = use_hint_88(r[i], h[i])
188 }
189 }
190 else {
191 panic('mldsa: unsupported gamma2') // unreachable
192 }
193 }
194
195 return w
196}
197
198fn use_hint_32(r FieldElement, hint u8) u8 {
199 mut r1, r0 := decompose_32(r)
200 if hint == 1 {
201 if r0 > 0 {
202 r1 = (r1 + 1) % 16
203 } else {
204 r1 = (r1 - 1) % 16
205 }
206 }
207 return r1
208}
209
210fn use_hint_88(r FieldElement, hint u8) u8 {
211 mut r1, r0 := decompose_88(r)
212 if hint == 1 {
213 if r0 > 0 {
214 if r1 == 43 {
215 r1 = 0
216 } else {
217 r1++
218 }
219 } else {
220 if r1 == 0 {
221 r1 = 43
222 } else {
223 r1--
224 }
225 }
226 }
227 return r1
228}
229
230// algo. 39: MakeHint (s. 7.4)
231@[direct_array_access]
232fn make_hint(ct0 RingElement, w RingElement, cs2 RingElement, p Params) ([256]u8, int) {
233 mut h := [256]u8{}
234 mut count := 0
235 match p.gamma2 {
236 32 {
237 for i in 0 .. n {
238 h[i] = make_hint_32(ct0[i], w[i], cs2[i])
239 count += int(h[i])
240 }
241 }
242 88 {
243 for i in 0 .. n {
244 h[i] = make_hint_88(ct0[i], w[i], cs2[i])
245 count += int(h[i])
246 }
247 }
248 else {
249 panic('mldsa: unsupported gamma2') // unreachable
250 }
251 }
252
253 return h, count
254}
255
256fn make_hint_32(ct0 FieldElement, w FieldElement, cs2 FieldElement) u8 {
257 r_plus_z := field_sub(w, cs2)
258 v1 := high_bits_32(field_from_montgomery(r_plus_z))
259 r1 := high_bits_32(field_from_montgomery(field_add(r_plus_z, ct0)))
260 return u8(1 - subtle.constant_time_byte_eq(v1, r1))
261}
262
263fn make_hint_88(ct0 FieldElement, w FieldElement, cs2 FieldElement) u8 {
264 r_plus_z := field_sub(w, cs2)
265 v1 := high_bits_88(field_from_montgomery(r_plus_z))
266 r1 := high_bits_88(field_from_montgomery(field_add(r_plus_z, ct0)))
267 return u8(1 - subtle.constant_time_byte_eq(v1, r1))
268}
269
270fn w1_encode_len(p Params) int {
271 return match p.gamma2 {
272 32 { 4 * n / 8 }
273 88 { 6 * n / 8 }
274 else { panic('mldsa: unsupported gamma2') } // unreachable
275 }
276}
277
278// algo. 28: w1Encode (s. 7.2)
279@[direct_array_access]
280fn w1_encode(w [256]u8, p Params, mut buf []u8) {
281 match p.gamma2 {
282 32 {
283 for i := 0; i < n; i += 2 {
284 buf[i / 2] = w[i] | (w[i + 1] << 4)
285 }
286 }
287 88 {
288 for i := 0; i < n; i += 4 {
289 buf[3 * i / 4] = w[i] | (w[i + 1] << 6)
290 buf[3 * i / 4 + 1] = (w[i + 1] >> 2) | (w[i + 2] << 4)
291 buf[3 * i / 4 + 2] = (w[i + 2] >> 4) | (w[i + 3] << 2)
292 }
293 }
294 else {
295 panic('mldsa: unsupported gamma2') // unreachable
296 }
297 }
298}
299
300// algo. 26: sigEncode (s. 7.2)
301fn sig_encode(ch []u8, z []RingElement, h [][256]u8, p Params) []u8 {
302 mut sig := ch.clone()
303 for i in 0 .. z.len {
304 sig << bit_pack(z[i], p)
305 }
306 sig << hint_encode(h, p)
307 return sig
308}
309
310// algo. 27: sigDecode (s. 7.2)
311@[direct_array_access]
312fn sig_decode(sig []u8, p Params) !([]u8, []RingElement, [][256]u8) {
313 expected := sig_size(p)
314 if sig.len != expected {
315 return error('invalid signature length')
316 }
317 ch_len := p.lambda / 4
318 ch := sig[..ch_len].clone()
319 mut offset := ch_len
320 mut z := []RingElement{len: p.l}
321 for i in 0 .. p.l {
322 length := (p.gamma1 + 1) * n / 8
323 z[i] = bit_unpack(sig[offset..offset + length], p)
324 offset += length
325 }
326 h := hint_decode(sig[offset..], p)!
327 return ch, z, h
328}
329
330// algo. 17: BitPack (s. 7.1)
331fn bit_pack(r RingElement, p Params) []u8 {
332 match p.gamma1 {
333 17 { return bit_pack_18(r) }
334 19 { return bit_pack_20(r) }
335 else { panic('mldsa: unsupported gamma1') } // unreachable
336 }
337}
338
339@[direct_array_access]
340fn bit_pack_18(r RingElement) []u8 {
341 mut v := []u8{len: 18 * n / 8}
342 mut pos := 0
343 for i := 0; i < n; i += 4 {
344 w0 := u32(1 << 17) - u32(field_centered_mod(r[i]))
345 w1 := u32(1 << 17) - u32(field_centered_mod(r[i + 1]))
346 w2 := u32(1 << 17) - u32(field_centered_mod(r[i + 2]))
347 w3 := u32(1 << 17) - u32(field_centered_mod(r[i + 3]))
348 v[pos] = u8(w0)
349 v[pos + 1] = u8(w0 >> 8)
350 v[pos + 2] = u8(w0 >> 16)
351 v[pos + 2] |= u8(w1 << 2)
352 v[pos + 3] = u8(w1 >> 6)
353 v[pos + 4] = u8(w1 >> 14)
354 v[pos + 4] |= u8(w2 << 4)
355 v[pos + 5] = u8(w2 >> 4)
356 v[pos + 6] = u8(w2 >> 12)
357 v[pos + 6] |= u8(w3 << 6)
358 v[pos + 7] = u8(w3 >> 2)
359 v[pos + 8] = u8(w3 >> 10)
360 pos += 9
361 }
362 return v
363}
364
365@[direct_array_access]
366fn bit_pack_20(r RingElement) []u8 {
367 mut v := []u8{len: 20 * n / 8}
368 mut pos := 0
369 for i := 0; i < n; i += 2 {
370 w0 := u32(1 << 19) - u32(field_centered_mod(r[i]))
371 w1 := u32(1 << 19) - u32(field_centered_mod(r[i + 1]))
372 v[pos] = u8(w0)
373 v[pos + 1] = u8(w0 >> 8)
374 v[pos + 2] = u8(w0 >> 16)
375 v[pos + 2] |= u8(w1 << 4)
376 v[pos + 3] = u8(w1 >> 4)
377 v[pos + 4] = u8(w1 >> 12)
378 pos += 5
379 }
380 return v
381}
382
383// algo. 19: BitUnpack (s. 7.1)
384fn bit_unpack(v []u8, p Params) RingElement {
385 match p.gamma1 {
386 17 { return bit_unpack_18(v) }
387 19 { return bit_unpack_20(v) }
388 else { panic('mldsa: unsupported gamma1') } // unreachable
389 }
390}
391
392@[direct_array_access]
393fn bit_unpack_18(v []u8) RingElement {
394 mut r := RingElement{}
395 mut pos := 0
396 for i := 0; i < n; i += 4 {
397 w0 := u32(v[pos]) | (u32(v[pos + 1]) << 8) | (u32(v[pos + 2]) << 16)
398 r[i] = field_sub_to_montgomery(u32(1 << 17), w0 & 0x3ffff)
399 w1 := (u32(v[pos + 2]) >> 2) | (u32(v[pos + 3]) << 6) | (u32(v[pos + 4]) << 14)
400 r[i + 1] = field_sub_to_montgomery(u32(1 << 17), w1 & 0x3ffff)
401 w2 := (u32(v[pos + 4]) >> 4) | (u32(v[pos + 5]) << 4) | (u32(v[pos + 6]) << 12)
402 r[i + 2] = field_sub_to_montgomery(u32(1 << 17), w2 & 0x3ffff)
403 w3 := (u32(v[pos + 6]) >> 6) | (u32(v[pos + 7]) << 2) | (u32(v[pos + 8]) << 10)
404 r[i + 3] = field_sub_to_montgomery(u32(1 << 17), w3 & 0x3ffff)
405 pos += 9
406 }
407 return r
408}
409
410@[direct_array_access]
411fn bit_unpack_20(v []u8) RingElement {
412 mut r := RingElement{}
413 mut pos := 0
414 for i := 0; i < n; i += 2 {
415 w0 := u32(v[pos]) | (u32(v[pos + 1]) << 8) | (u32(v[pos + 2]) << 16)
416 r[i] = field_sub_to_montgomery(u32(1 << 19), w0 & 0xfffff)
417 w1 := (u32(v[pos + 2]) >> 4) | (u32(v[pos + 3]) << 4) | (u32(v[pos + 4]) << 12)
418 r[i + 1] = field_sub_to_montgomery(u32(1 << 19), w1 & 0xfffff)
419 pos += 5
420 }
421 return r
422}
423
424// algo. 20: HintBitPack (s. 7.2)
425@[direct_array_access]
426fn hint_encode(h [][256]u8, p Params) []u8 {
427 mut out := []u8{len: p.omega + p.k}
428 mut idx := 0
429 for i in 0 .. p.k {
430 for j in 0 .. n {
431 if h[i][j] != 0 {
432 out[idx] = u8(j)
433 idx++
434 }
435 }
436 out[p.omega + i] = u8(idx)
437 }
438 return out
439}
440
441// algo. 21: HintBitUnpack (s. 7.2)
442@[direct_array_access]
443fn hint_decode(y []u8, p Params) ![][256]u8 {
444 if y.len != p.omega + p.k {
445 return error('invalid hint length')
446 }
447 mut h := [][256]u8{len: p.k, init: [256]u8{}}
448 mut idx := u8(0)
449 for i in 0 .. p.k {
450 limit := y[p.omega + i]
451 if limit < idx || limit > u8(p.omega) {
452 return error('invalid hint limits')
453 }
454 first := idx
455 for idx < limit {
456 if idx > first && y[idx - 1] >= y[idx] {
457 return error('invalid hint index order')
458 }
459 h[i][y[idx]] = 1
460 idx++
461 }
462 }
463 for i := int(idx); i < p.omega; i++ {
464 if y[i] != 0 {
465 return error('invalid hint padding')
466 }
467 }
468 return h
469}
470
471@[direct_array_access]
472fn bit_pack_slow(r RingElement, a int, b int) []u8 {
473 bitlen := bits_len(u32(a + b))
474 mut out := []u8{len: n * bitlen / 8}
475 mut acc := u32(0)
476 mut acc_bits := u32(0)
477 mut vi := 0
478 for i in 0 .. n {
479 w := u32(int(b) - int(field_centered_mod(r[i])))
480 acc |= w << acc_bits
481 acc_bits += u32(bitlen)
482 for acc_bits >= 8 {
483 out[vi] = u8(acc)
484 vi++
485 acc >>= 8
486 acc_bits -= 8
487 }
488 }
489 if acc_bits > 0 {
490 out[vi] = u8(acc)
491 }
492 return out
493}
494
495@[direct_array_access]
496fn bit_unpack_slow(v []u8, a int, b int) !RingElement {
497 bitlen := bits_len(u32(a + b))
498 if v.len != n * bitlen / 8 {
499 return error('mldsa: invalid input length for bit_unpack_slow')
500 }
501
502 mask := u32((1 << bitlen) - 1)
503 max_value := u32(a + b)
504
505 mut r := RingElement{}
506 mut acc := u32(0)
507 mut acc_bits := u32(0)
508 mut vi := 0
509
510 for i in 0 .. n {
511 for acc_bits < u32(bitlen) {
512 if vi < v.len {
513 acc |= u32(v[vi]) << acc_bits
514 vi++
515 acc_bits += 8
516 }
517 }
518 w := acc & mask
519 if w > max_value {
520 return error('mldsa: coefficient out of range')
521 }
522 r[i] = field_sub_to_montgomery(u32(b), w)
523 acc >>= u32(bitlen)
524 acc_bits -= u32(bitlen)
525 }
526
527 return r
528}
529
530fn bits_len(x u32) int {
531 if x == 0 {
532 return 0
533 }
534 mut v := x
535 mut n_ := 0
536 for v > 0 {
537 v >>= 1
538 n_++
539 }
540 return n_
541}
542
543// algo. 24: skEncode (s. 7.2)
544fn sk_encode(rho []u8, capital_k [32]u8, tr [64]u8, s1 []NttElement, s2 []NttElement, t0 []NttElement, p Params) []u8 {
545 mut out := []u8{}
546 out << rho[..32]
547 out << capital_k[..]
548 out << tr[..]
549 eta := int(p.eta)
550 for i in 0 .. p.l {
551 out << bit_pack_slow(inverse_ntt(s1[i]), eta, eta)
552 }
553 for i in 0 .. p.k {
554 out << bit_pack_slow(inverse_ntt(s2[i]), eta, eta)
555 }
556 for i in 0 .. p.k {
557 out << bit_pack_slow(inverse_ntt(t0[i]), 4095, 4096)
558 }
559 return out
560}
561
562// algo. 25: skDecode (s. 7.2)
563fn sk_decode(sk []u8, p Params) !([]u8, [32]u8, [64]u8, []RingElement, []RingElement, []RingElement) {
564 k, l, eta := p.k, p.l, p.eta
565 if sk.len != priv_key_size(p) {
566 return error('mldsa: invalid private key size')
567 }
568 rho := sk[..32].clone()
569 capital_k := slice_to_32(sk[32..64])
570 tr := slice_to_64(sk[64..128])
571 mut offset := 128
572
573 eta_len := n * bits_len(u32(eta * 2)) / 8
574 mut s1 := []RingElement{len: l}
575 for i in 0 .. l {
576 s1[i] = bit_unpack_slow(sk[offset..offset + eta_len], eta, eta)!
577 offset += eta_len
578 }
579
580 mut s2 := []RingElement{len: k}
581 for i in 0 .. k {
582 s2[i] = bit_unpack_slow(sk[offset..offset + eta_len], eta, eta)!
583 offset += eta_len
584 }
585
586 t0_len := n * 13 / 8
587 mut t0 := []RingElement{len: k}
588 for i in 0 .. k {
589 t0[i] = bit_unpack_slow(sk[offset..offset + t0_len], 4095, 4096)!
590 offset += t0_len
591 }
592
593 return rho, capital_k, tr, s1, s2, t0
594}
595