| 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. |
| 6 | module mldsa |
| 7 | |
| 8 | import crypto.internal.subtle |
| 9 | |
| 10 | // s. 2.3, appendix a |
| 11 | const q = u32(8380417) // 2^23 - 2^13 + 1 |
| 12 | const rr = u32(2365951) // R^2 mod q (R = 2^32) |
| 13 | const q_neg_inv = u32(4236238847) // -q^-1 mod R (appendix a: QINV = 58728449) |
| 14 | const mont_one = u32(4193792) // R mod q |
| 15 | const mont_minus_one = u32(4186625) // (q-1)*R mod q |
| 16 | const n = 256 |
| 17 | const d = 13 |
| 18 | |
| 19 | // signing uses rejection sampling that succeeds with p =~ 1/5.22 |
| 20 | // per attempt (worst case: ML-DSA-65, over 10k sigs) |
| 21 | // P(no converg. after 512 iters) = (1 - 1/5.22)^512 =~ 2^-157 |
| 22 | // for ref. ML-DSA-44 security is 2^-128 |
| 23 | const max_sign_attempts = 512 |
| 24 | |
| 25 | type FieldElement = u32 |
| 26 | type RingElement = [256]u32 |
| 27 | type NttElement = [256]u32 |
| 28 | |
| 29 | fn field_to_montgomery(a u32) !FieldElement { |
| 30 | if a >= q { |
| 31 | return error('unreduced field element ${a}') |
| 32 | } |
| 33 | return field_montgomery_mul(FieldElement(a), rr) |
| 34 | } |
| 35 | |
| 36 | fn field_sub_to_montgomery(a u32, b u32) FieldElement { |
| 37 | x := a - b + q |
| 38 | return field_montgomery_mul(FieldElement(x), rr) |
| 39 | } |
| 40 | |
| 41 | fn field_from_montgomery(a FieldElement) u32 { |
| 42 | return u32(field_montgomery_reduce(u64(a))) |
| 43 | } |
| 44 | |
| 45 | fn field_centered_mod(r FieldElement) i32 { |
| 46 | x := i32(field_from_montgomery(r)) |
| 47 | return ct_select_leq(x, i32(q / 2), x, x - i32(q)) |
| 48 | } |
| 49 | |
| 50 | fn field_infinity_norm(r FieldElement) u32 { |
| 51 | x := i32(field_from_montgomery(r)) |
| 52 | return u32(ct_select_leq(x, i32(q / 2), x, i32(q) - x)) |
| 53 | } |
| 54 | |
| 55 | fn field_reduce_once(a u32) FieldElement { |
| 56 | if a >= q { |
| 57 | return FieldElement(a - q) |
| 58 | } |
| 59 | return FieldElement(a) |
| 60 | } |
| 61 | |
| 62 | fn field_add(a FieldElement, b FieldElement) FieldElement { |
| 63 | return field_reduce_once(u32(a) + u32(b)) |
| 64 | } |
| 65 | |
| 66 | fn field_sub(a FieldElement, b FieldElement) FieldElement { |
| 67 | return field_reduce_once(u32(a) - u32(b) + q) |
| 68 | } |
| 69 | |
| 70 | fn field_montgomery_mul(a FieldElement, b FieldElement) FieldElement { |
| 71 | x := u64(a) * u64(b) |
| 72 | return field_montgomery_reduce(x) |
| 73 | } |
| 74 | |
| 75 | // algo. 49: MontgomeryReduce |
| 76 | fn field_montgomery_reduce(x u64) FieldElement { |
| 77 | t := u32(x) * q_neg_inv |
| 78 | u_ := (x + u64(t) * u64(q)) >> 32 |
| 79 | return field_reduce_once(u32(u_)) |
| 80 | } |
| 81 | |
| 82 | fn field_montgomery_mul_sub(a FieldElement, b FieldElement, c FieldElement) FieldElement { |
| 83 | x := u64(a) * u64(u32(b) - u32(c) + q) |
| 84 | return field_montgomery_reduce(x) |
| 85 | } |
| 86 | |
| 87 | fn field_montgomery_add_mul(a FieldElement, b FieldElement, c FieldElement, d_ FieldElement) FieldElement { |
| 88 | x := u64(a) * u64(b) + u64(c) * u64(d_) |
| 89 | return field_montgomery_reduce(x) |
| 90 | } |
| 91 | |
| 92 | @[direct_array_access] |
| 93 | fn poly_add_ring(a RingElement, b RingElement) RingElement { |
| 94 | mut s := RingElement{} |
| 95 | for i in 0 .. n { |
| 96 | s[i] = field_add(a[i], b[i]) |
| 97 | } |
| 98 | return s |
| 99 | } |
| 100 | |
| 101 | @[direct_array_access] |
| 102 | fn poly_add_ntt(a NttElement, b NttElement) NttElement { |
| 103 | mut s := NttElement{} |
| 104 | for i in 0 .. n { |
| 105 | s[i] = field_add(a[i], b[i]) |
| 106 | } |
| 107 | return s |
| 108 | } |
| 109 | |
| 110 | @[direct_array_access] |
| 111 | fn poly_sub_ring(a RingElement, b RingElement) RingElement { |
| 112 | mut s := RingElement{} |
| 113 | for i in 0 .. n { |
| 114 | s[i] = field_sub(a[i], b[i]) |
| 115 | } |
| 116 | return s |
| 117 | } |
| 118 | |
| 119 | @[direct_array_access] |
| 120 | fn poly_sub_ntt(a NttElement, b NttElement) NttElement { |
| 121 | mut s := NttElement{} |
| 122 | for i in 0 .. n { |
| 123 | s[i] = field_sub(a[i], b[i]) |
| 124 | } |
| 125 | return s |
| 126 | } |
| 127 | |
| 128 | // algo. 45: MultiplyNTT |
| 129 | @[direct_array_access] |
| 130 | fn ntt_mul(a NttElement, b NttElement) NttElement { |
| 131 | mut p := NttElement{} |
| 132 | for i in 0 .. n { |
| 133 | p[i] = field_montgomery_mul(a[i], b[i]) |
| 134 | } |
| 135 | return p |
| 136 | } |
| 137 | |
| 138 | @[direct_array_access] |
| 139 | fn coefficients_exceed_bound(w RingElement, bound u32) bool { |
| 140 | for i in 0 .. n { |
| 141 | if field_infinity_norm(w[i]) >= bound { |
| 142 | return true |
| 143 | } |
| 144 | } |
| 145 | return false |
| 146 | } |
| 147 | |
| 148 | fn ct_select_leq(a i32, b i32, yes i32, no i32) i32 { |
| 149 | return if subtle.constant_time_less_or_eq(int(a), int(b)) == 1 { yes } else { no } |
| 150 | } |
| 151 | |
| 152 | fn ct_select_eq(a u32, b u32, yes u32, no u32) u32 { |
| 153 | return u32(subtle.constant_time_select(subtle.constant_time_eq(int(a), int(b)), int(yes), |
| 154 | int(no))) |
| 155 | } |
| 156 | |
| 157 | fn ct_abs(x i32) u32 { |
| 158 | return u32(ct_select_leq(0, x, x, -x)) |
| 159 | } |
| 160 | |