secp256k1-native 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/ext/secp256k1_native/jacobian.c +84 -42
- data/ext/secp256k1_native/secp256k1_native.h +15 -0
- data/lib/secp256k1/version.rb +1 -1
- data/lib/secp256k1_native.bundle +0 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6a8b3524bbf944accbd5ee819c3623ce7495829d30b280fb29a905e43dcf52b
|
|
4
|
+
data.tar.gz: a0204b6cf5b4c37447d63e29425086320642cb881b9c107847a00111b4d07373
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f0a0fd90e016a83ef50ce79f4d70c058524ae47d008992edb6250b12363ebf6c12397376da0ae6978592d9bf9a5ea05038765384f5379aba537503af9fc2b54
|
|
7
|
+
data.tar.gz: e22b4b04332c215353bfaf38f45eb03825fc9d1012a926e0c4da2369fbdc9c39bb305e3faecd69a9c476755f6c67872570d492c8531b93e0e9edaa6e7bd61872
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.17.0] - 2026-05-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Dudect-based constant-time verification harness (`rake timing:verify`) — empirical timing leakage detection using Welch's t-test for all constant-time C extension functions
|
|
8
|
+
- Cryptographic development principles codified in CLAUDE.md — seven principles governing all development decisions
|
|
9
|
+
- Property-based testing suite (field arithmetic, scalar arithmetic, point operations, cross-implementation parity)
|
|
10
|
+
- GitHub Actions CI workflow for Ruby 2.7–3.4 matrix
|
|
11
|
+
- Security findings disclosure process
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Timing side-channel in `scalar_multiply_ct`** — `jp_add_internal` had early-return branches on infinity checks that leaked timing information about the secret scalar inside the Montgomery ladder (dudect t = -875). Made `jp_add_internal` fully branchless with mask-based conditional selection. Verified fix via dudect (t = 1.0)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Field arithmetic (`fred`, `fsub`, `fneg`, `fadd`) constant-time properties now empirically verified via dudect, not just code inspection
|
|
20
|
+
|
|
3
21
|
## [0.16.0] - 2026-04-29
|
|
4
22
|
|
|
5
23
|
### Breaking Changes
|
|
@@ -24,9 +24,12 @@
|
|
|
24
24
|
* ------------------------
|
|
25
25
|
* jp_double: The Y=0 (point at infinity) check is handled branchlessly by
|
|
26
26
|
* computing the full result and masking to JP_INFINITY when Y is zero.
|
|
27
|
-
* jp_add:
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* jp_add: Fully branchless. All 18 field operations for the normal case
|
|
28
|
+
* are computed unconditionally, along with an unconditional jp_double for
|
|
29
|
+
* the h==0 (equal points) case. Mask-based selects choose the correct
|
|
30
|
+
* result (normal, double, infinity, or passthrough) without branching on
|
|
31
|
+
* any input-dependent value. This is essential for constant-time scalar
|
|
32
|
+
* multiplication via the Montgomery ladder.
|
|
30
33
|
* jp_neg: Branchless — delegates the zero-checking to fneg_internal.
|
|
31
34
|
*/
|
|
32
35
|
|
|
@@ -138,7 +141,7 @@ void jp_double_internal(uint256_t r[3], const uint256_t p[3])
|
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
/*
|
|
141
|
-
* jp_add_internal — add two Jacobian points.
|
|
144
|
+
* jp_add_internal — add two Jacobian points (branchless).
|
|
142
145
|
*
|
|
143
146
|
* Formula (from hyperelliptic.org, "add-2007-bl"):
|
|
144
147
|
*
|
|
@@ -153,27 +156,41 @@ void jp_double_internal(uint256_t r[3], const uint256_t p[3])
|
|
|
153
156
|
* Y3 = R·(V - X3) - S1·H3
|
|
154
157
|
* Z3 = H·Z1·Z2
|
|
155
158
|
*
|
|
156
|
-
* Special cases (handled
|
|
157
|
-
* - pz == 0 (p is infinity) →
|
|
158
|
-
* - qz == 0 (q is infinity) →
|
|
159
|
-
* - h == 0, r == 0 → points are equal
|
|
160
|
-
* - h == 0, r != 0 → points are negatives
|
|
161
|
-
*
|
|
162
|
-
*
|
|
159
|
+
* Special cases (handled branchlessly via mask-based selects):
|
|
160
|
+
* - pz == 0 (p is infinity) → result = q
|
|
161
|
+
* - qz == 0 (q is infinity) → result = p
|
|
162
|
+
* - h == 0, r == 0 → points are equal → jp_double(p)
|
|
163
|
+
* - h == 0, r != 0 → points are negatives → infinity
|
|
164
|
+
*
|
|
165
|
+
* All field operations and the jp_double call are computed unconditionally.
|
|
166
|
+
* The correct result is chosen at the end via uint256_select, ensuring
|
|
167
|
+
* constant-time execution for the Montgomery ladder in scalar_multiply_ct.
|
|
168
|
+
*
|
|
169
|
+
* When h==0, the main path computes with h2=0, h3=0, z3=0 — well-defined
|
|
170
|
+
* field elements (no undefined behaviour), just mathematically meaningless.
|
|
171
|
+
* The mask-based selects override with the correct result.
|
|
172
|
+
*
|
|
173
|
+
* Selection order (later overrides earlier):
|
|
174
|
+
* 1. Start with normal addition result
|
|
175
|
+
* 2. If h==0 && r_val==0: select jp_double result (equal points)
|
|
176
|
+
* 3. If h==0 && r_val!=0: select infinity (negated points)
|
|
177
|
+
* 4. If qz==0: select p (q was infinity)
|
|
178
|
+
* 5. If pz==0: select q (p was infinity)
|
|
163
179
|
*/
|
|
164
180
|
void jp_add_internal(uint256_t r[3], const uint256_t p[3], const uint256_t q[3])
|
|
165
181
|
{
|
|
166
|
-
/*
|
|
167
|
-
*
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
182
|
+
/* Save copies of inputs for the final mask-based selects, since r may
|
|
183
|
+
* alias p or q (e.g. jp_add_internal(r1, r0, r1) in the Montgomery ladder).
|
|
184
|
+
* Writing the normal result into r[] would corrupt the source otherwise. */
|
|
185
|
+
uint256_t p_copy[3], q_copy[3];
|
|
186
|
+
memcpy(p_copy, p, sizeof(uint256_t) * 3);
|
|
187
|
+
memcpy(q_copy, q, sizeof(uint256_t) * 3);
|
|
188
|
+
|
|
189
|
+
/* 1. Capture input-dependent flags (evaluated once, used only in selects). */
|
|
190
|
+
uint64_t pz_zero = uint256_is_zero(&p[2]);
|
|
191
|
+
uint64_t qz_zero = uint256_is_zero(&q[2]);
|
|
192
|
+
|
|
193
|
+
/* 2. Compute all 18 field operations unconditionally. */
|
|
177
194
|
uint256_t z1z1, z2z2;
|
|
178
195
|
uint256_t u1, u2;
|
|
179
196
|
uint256_t s1, s2;
|
|
@@ -199,21 +216,8 @@ void jp_add_internal(uint256_t r[3], const uint256_t p[3], const uint256_t q[3])
|
|
|
199
216
|
fsub_internal(&h, &u2, &u1);
|
|
200
217
|
fsub_internal(&r_val, &s2, &s1);
|
|
201
218
|
|
|
202
|
-
/*
|
|
203
|
-
* h
|
|
204
|
-
* r == 0 additionally means the same Y → equal points → double.
|
|
205
|
-
* r != 0 means opposite Y → additive inverse → infinity. */
|
|
206
|
-
if (uint256_is_zero(&h)) {
|
|
207
|
-
if (uint256_is_zero(&r_val)) {
|
|
208
|
-
jp_double_internal(r, p);
|
|
209
|
-
} else {
|
|
210
|
-
r[0] = JP_INF_X;
|
|
211
|
-
r[1] = JP_INF_Y;
|
|
212
|
-
r[2] = JP_INF_Z;
|
|
213
|
-
}
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
219
|
+
/* 3. Remaining field ops for the normal addition path.
|
|
220
|
+
* When h==0 these produce h2=0, h3=0, z3=0 — valid field elements. */
|
|
217
221
|
uint256_t h2, h3, v, x3, y3, z3;
|
|
218
222
|
|
|
219
223
|
/* h2 = h², h3 = h * h2 */
|
|
@@ -241,9 +245,48 @@ void jp_add_internal(uint256_t r[3], const uint256_t p[3], const uint256_t q[3])
|
|
|
241
245
|
fmul_internal(&tmp, &p[2], &q[2]);
|
|
242
246
|
fmul_internal(&z3, &h, &tmp);
|
|
243
247
|
|
|
248
|
+
/* 4. Compute jp_double unconditionally for the h==0 && r_val==0 case.
|
|
249
|
+
* Uses p_copy since r may alias p. */
|
|
250
|
+
uint256_t dbl[3];
|
|
251
|
+
jp_double_internal(dbl, p_copy);
|
|
252
|
+
|
|
253
|
+
/* 5. Capture remaining flags for mask-based selection. */
|
|
254
|
+
uint64_t h_zero = uint256_is_zero(&h);
|
|
255
|
+
uint64_t r_zero = uint256_is_zero(&r_val);
|
|
256
|
+
|
|
257
|
+
/* 6. Branchless result selection — order matters (later overrides earlier).
|
|
258
|
+
*
|
|
259
|
+
* Start with the normal addition result, then overlay special cases.
|
|
260
|
+
* Each uint256_select replaces r[i] only when the flag is non-zero. */
|
|
261
|
+
|
|
262
|
+
/* Start with normal addition result. */
|
|
244
263
|
r[0] = x3;
|
|
245
264
|
r[1] = y3;
|
|
246
265
|
r[2] = z3;
|
|
266
|
+
|
|
267
|
+
/* If h==0 && r_val==0: points are equal → use jp_double result. */
|
|
268
|
+
uint64_t select_dbl = h_zero & r_zero;
|
|
269
|
+
uint256_select(&r[0], &r[0], &dbl[0], select_dbl);
|
|
270
|
+
uint256_select(&r[1], &r[1], &dbl[1], select_dbl);
|
|
271
|
+
uint256_select(&r[2], &r[2], &dbl[2], select_dbl);
|
|
272
|
+
|
|
273
|
+
/* If h==0 && r_val!=0: points are negatives → result is infinity. */
|
|
274
|
+
uint64_t select_inf = h_zero & (~r_zero & 1);
|
|
275
|
+
uint256_select(&r[0], &r[0], &JP_INF_X, select_inf);
|
|
276
|
+
uint256_select(&r[1], &r[1], &JP_INF_Y, select_inf);
|
|
277
|
+
uint256_select(&r[2], &r[2], &JP_INF_Z, select_inf);
|
|
278
|
+
|
|
279
|
+
/* If qz==0: q was infinity → result is p.
|
|
280
|
+
* Uses p_copy since r may alias p. */
|
|
281
|
+
uint256_select(&r[0], &r[0], &p_copy[0], qz_zero);
|
|
282
|
+
uint256_select(&r[1], &r[1], &p_copy[1], qz_zero);
|
|
283
|
+
uint256_select(&r[2], &r[2], &p_copy[2], qz_zero);
|
|
284
|
+
|
|
285
|
+
/* If pz==0: p was infinity → result is q.
|
|
286
|
+
* Uses q_copy since r may alias q. */
|
|
287
|
+
uint256_select(&r[0], &r[0], &q_copy[0], pz_zero);
|
|
288
|
+
uint256_select(&r[1], &r[1], &q_copy[1], pz_zero);
|
|
289
|
+
uint256_select(&r[2], &r[2], &q_copy[2], pz_zero);
|
|
247
290
|
}
|
|
248
291
|
|
|
249
292
|
/*
|
|
@@ -399,11 +442,10 @@ void scalar_multiply_ct_internal(uint256_t r[3], const uint256_t *k, const uint2
|
|
|
399
442
|
* Constant-time scalar multiplication using the Montgomery ladder.
|
|
400
443
|
*
|
|
401
444
|
* Computes k × (px, py) entirely in C with no per-iteration Ruby dispatch.
|
|
402
|
-
* The ladder loop is branchless with respect to the scalar bits (via cswap)
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* never a valid private key or nonce).
|
|
445
|
+
* The ladder loop is branchless with respect to the scalar bits (via cswap),
|
|
446
|
+
* and jp_add_internal is fully branchless (mask-based selects for all
|
|
447
|
+
* input-dependent special cases). The k==0 early return is on a non-secret
|
|
448
|
+
* value (k==0 is never a valid private key or nonce).
|
|
407
449
|
*
|
|
408
450
|
* @param k [Integer] scalar (must be in [0, N))
|
|
409
451
|
* @param px [Integer] affine x-coordinate of the base point
|
|
@@ -103,6 +103,21 @@ void scalar_inv_internal(uint256_t *r, const uint256_t *a);
|
|
|
103
103
|
/* Registration helper — called from Init_secp256k1_native. */
|
|
104
104
|
void register_scalar_methods(VALUE mod);
|
|
105
105
|
|
|
106
|
+
/* -----------------------------------------------------------------------
|
|
107
|
+
* Branchless selection helper
|
|
108
|
+
* ----------------------------------------------------------------------- */
|
|
109
|
+
|
|
110
|
+
/* Branchless conditional select: if flag is non-zero, *r = *b; else *r = *a.
|
|
111
|
+
* Constant-time: no branch on flag. */
|
|
112
|
+
static inline void uint256_select(uint256_t *r, const uint256_t *a,
|
|
113
|
+
const uint256_t *b, uint64_t flag) {
|
|
114
|
+
uint64_t mask = -(uint64_t)(flag != 0);
|
|
115
|
+
r->d[0] = (a->d[0] & ~mask) | (b->d[0] & mask);
|
|
116
|
+
r->d[1] = (a->d[1] & ~mask) | (b->d[1] & mask);
|
|
117
|
+
r->d[2] = (a->d[2] & ~mask) | (b->d[2] & mask);
|
|
118
|
+
r->d[3] = (a->d[3] & ~mask) | (b->d[3] & mask);
|
|
119
|
+
}
|
|
120
|
+
|
|
106
121
|
/* -----------------------------------------------------------------------
|
|
107
122
|
* Jacobian point operations — internal functions declared here so that
|
|
108
123
|
* future modules (e.g. a scalar multiply module) can call them directly
|
data/lib/secp256k1/version.rb
CHANGED
data/lib/secp256k1_native.bundle
CHANGED
|
Binary file
|