1use ruint::Uint;
10
11use super::{CurvePoint, SECP256K1_GENERATOR_X, SECP256K1_GENERATOR_Y};
12use crate::math::{
13 k1_scalar::K1Scalar,
14 uint::{Limbs, UintDomain},
15};
16
17pub const SECP256K1_LAMBDA: Limbs = [
19 0x1b23bd72, 0xdf02967c, 0x20816678, 0x122e22ea, 0x8812645a, 0xa5261c02, 0xc05c30e0, 0x5363ad4c,
20];
21
22pub const SECP256K1_BETA: Limbs = [
25 0x719501ee, 0xc1396c28, 0x12f58995, 0x9cf04975, 0xac3434e9, 0x6e64479e, 0x657c0710, 0x7ae96a2b,
26];
27
28pub fn phi_generator() -> CurvePoint {
32 CurvePoint::Affine {
33 x: UintDomain::K1Base.mul(SECP256K1_BETA, SECP256K1_GENERATOR_X),
34 y: SECP256K1_GENERATOR_Y,
35 }
36}
37
38type Wide = Uint<512, 8>;
43
44fn wide_zero() -> Wide {
45 Wide::from_limbs([0; 8])
46}
47
48fn wide_one() -> Wide {
49 let mut limbs = [0u64; 8];
50 limbs[0] = 1;
51 Wide::from_limbs(limbs)
52}
53
54fn limbs_to_wide(limbs: Limbs) -> Wide {
55 let mut u64_limbs = [0u64; 8];
56 for i in 0..4 {
57 u64_limbs[i] = (limbs[2 * i] as u64) | ((limbs[2 * i + 1] as u64) << 32);
58 }
59 Wide::from_limbs(u64_limbs)
60}
61
62fn wide_to_limbs(v: Wide) -> Limbs {
67 let u64_limbs = v.as_limbs();
68 assert!(u64_limbs[4..].iter().all(|&l| l == 0), "GLV magnitude must fit in 256 bits");
69 core::array::from_fn(|i| {
70 let word = u64_limbs[i / 2];
71 if i % 2 == 0 { word as u32 } else { (word >> 32) as u32 }
72 })
73}
74
75#[derive(Clone, Copy)]
79struct Signed {
80 neg: bool,
81 mag: Wide,
82}
83
84impl Signed {
85 fn new(neg: bool, mag: Wide) -> Self {
86 if mag == wide_zero() {
88 Signed { neg: false, mag }
89 } else {
90 Signed { neg, mag }
91 }
92 }
93
94 fn negate(self) -> Self {
95 Signed::new(!self.neg, self.mag)
96 }
97
98 fn add(self, other: Self) -> Self {
99 if self.neg == other.neg {
100 Signed::new(self.neg, self.mag + other.mag)
101 } else if self.mag >= other.mag {
102 Signed::new(self.neg, self.mag - other.mag)
103 } else {
104 Signed::new(other.neg, other.mag - self.mag)
105 }
106 }
107
108 fn sub(self, other: Self) -> Self {
109 self.add(other.negate())
110 }
111
112 fn mul(self, other: Self) -> Self {
113 Signed::new(self.neg != other.neg, self.mag * other.mag)
114 }
115
116 fn div_round(self, n: Wide) -> Self {
120 let q = self.mag / n;
121 let r = self.mag % n;
122 let q = if r + r >= n { q + wide_one() } else { q };
123 Signed::new(self.neg, q)
124 }
125}
126
127const GLV_BASIS: [(bool, Limbs); 4] = [
132 (false, [0x9284eb15, 0xe86c90e4, 0xa7d46bcd, 0x3086d221, 0, 0, 0, 0]),
134 (true, [0x0abfe4c3, 0x6f547fa9, 0x010e8828, 0xe4437ed6, 0, 0, 0, 0]),
136 (false, [0x9d44cfd8, 0x57c1108d, 0xa8e2f3f6, 0x14ca50f7, 0x00000001, 0, 0, 0]),
138 (false, [0x9284eb15, 0xe86c90e4, 0xa7d46bcd, 0x3086d221, 0, 0, 0, 0]),
140];
141
142pub fn glv_decompose(k: Limbs) -> [(bool, Limbs); 2] {
152 let n = limbs_to_wide(K1Scalar::MODULUS);
153 let [(a1_neg, a1_mag), (b1_neg, b1_mag), (a2_neg, a2_mag), (b2_neg, b2_mag)] = GLV_BASIS;
154 let a1 = Signed::new(a1_neg, limbs_to_wide(a1_mag));
155 let b1 = Signed::new(b1_neg, limbs_to_wide(b1_mag));
156 let a2 = Signed::new(a2_neg, limbs_to_wide(a2_mag));
157 let b2 = Signed::new(b2_neg, limbs_to_wide(b2_mag));
158
159 let k_s = Signed::new(false, limbs_to_wide(k));
160 let c1 = b2.mul(k_s).div_round(n);
161 let c2 = b1.negate().mul(k_s).div_round(n);
162 let k1 = k_s.sub(c1.mul(a1)).sub(c2.mul(a2));
163 let k2 = c1.negate().mul(b1).sub(c2.mul(b2));
164
165 [(k1.neg, wide_to_limbs(k1.mag)), (k2.neg, wide_to_limbs(k2.mag))]
166}
167
168pub fn scalar_mul_mod_n(a: Limbs, b: Limbs) -> Limbs {
170 let n = limbs_to_wide(K1Scalar::MODULUS);
171 wide_to_limbs((limbs_to_wide(a) * limbs_to_wide(b)) % n)
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 fn limbs_from_u64(v: u64) -> Limbs {
179 [v as u32, (v >> 32) as u32, 0, 0, 0, 0, 0, 0]
180 }
181
182 fn recompose(split: [(bool, Limbs); 2]) -> Wide {
185 let n = limbs_to_wide(K1Scalar::MODULUS);
186 let lambda = limbs_to_wide(SECP256K1_LAMBDA);
187 let to_signed = |(neg, mag): (bool, Limbs)| Signed::new(neg, limbs_to_wide(mag));
188 let a = to_signed(split[0]);
189 let b = to_signed(split[1]);
190 let term = Signed::new(false, lambda).mul(b);
191 let sum = a.add(term);
192 let mag_mod_n = sum.mag % n;
194 if sum.neg && mag_mod_n != wide_zero() {
195 n - mag_mod_n
196 } else {
197 mag_mod_n
198 }
199 }
200
201 #[test]
205 fn glv_basis_matches_extended_euclid_reduction() {
206 let n = limbs_to_wide(K1Scalar::MODULUS);
207 let lambda = limbs_to_wide(SECP256K1_LAMBDA);
208
209 let below_sqrt_n = |r: Wide| r * r < n;
210 let (mut r0, mut r1) = (n, lambda);
211 let (mut t0, mut t1) = (Signed::new(false, wide_zero()), Signed::new(false, wide_one()));
212 while !below_sqrt_n(r1) {
213 let q = r0 / r1;
214 let r2 = r0 - q * r1;
215 let t2 = t0.sub(Signed::new(false, q).mul(t1));
216 (r0, r1, t0, t1) = (r1, r2, t1, t2);
217 }
218
219 let (a1, b1) = (Signed::new(false, r1), t1.negate());
220 let q = r0 / r1;
221 let r2 = r0 - q * r1;
222 let t2 = t0.sub(Signed::new(false, q).mul(t1));
223 let norm = |r: Wide, t: Wide| r * r + t * t;
224 let (a2, b2) = if norm(r0, t0.mag) <= norm(r2, t2.mag) {
225 (Signed::new(false, r0), t0.negate())
226 } else {
227 (Signed::new(false, r2), t2.negate())
228 };
229
230 let derived = [a1, b1, a2, b2].map(|s| (s.neg, wide_to_limbs(s.mag)));
231 assert_eq!(
232 derived, GLV_BASIS,
233 "GLV_BASIS is stale relative to the extended-Euclid reduction of (n, lambda)"
234 );
235 }
236
237 #[test]
238 fn glv_decompose_recomposes_small_scalars() {
239 for k in [0u64, 1, 2, 12345, u64::MAX] {
240 let split = glv_decompose(limbs_from_u64(k));
241 assert_eq!(recompose(split), limbs_to_wide(limbs_from_u64(k)), "failed for k={k}");
242 }
243 }
244
245 #[test]
246 fn glv_decompose_recomposes_full_width_scalar() {
247 let k: Limbs = [
248 0x12345678, 0x9abcdef0, 0x0fedcba9, 0x87654321, 0x11223344, 0x55667788, 0x99aabbcc,
249 0x00112233,
250 ];
251 let split = glv_decompose(k);
252 assert_eq!(recompose(split), limbs_to_wide(k));
253 }
254
255 #[test]
256 fn glv_decompose_halves_are_short() {
257 let k: Limbs = [
260 0x12345678, 0x9abcdef0, 0x0fedcba9, 0x87654321, 0x11223344, 0x55667788, 0x99aabbcc,
261 0x00112233,
262 ];
263 let mut bound_limbs = [0u32; 8];
265 bound_limbs[4] = 0x10;
266 let bound = limbs_to_wide(bound_limbs);
267 for (_, mag) in glv_decompose(k) {
268 assert!(limbs_to_wide(mag) < bound, "GLV half is not short: {mag:?}");
269 }
270 }
271}