Skip to content

Commit 0db4e7e

Browse files
committed
k256: remove non-endomorphism code
This code was previously gated under the `endomorphism-mul` feature due to lingering concerns about the "GLV" patents, namely US7110538. According to @pwuille, a patent attorney he works with has verified that the patent is expired: https://twitter.com/pwuille/status/1310639051393830912 Likewise non-endomorphism code is being removed from `bitcoin-core/secp256k1`: bitcoin-core/secp256k1#826 This commit does the same for `k256`: it removes all the non-endomorphism code and feature gating from the endomorphism code, making the `endomorphism-mul` feature a no-op. We can remove the feature in the next SemVer-breaking release (v0.6.x)
1 parent 452f702 commit 0db4e7e

File tree

6 files changed

+67
-125
lines changed

6 files changed

+67
-125
lines changed

k256/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ proptest = "0.10"
3434
rand_core = { version = "0.5", features = ["getrandom"] }
3535

3636
[features]
37-
default = ["arithmetic", "endomorphism-mul", "oid", "std"]
37+
default = ["arithmetic", "oid", "std"]
3838
arithmetic = ["elliptic-curve/arithmetic"]
3939
digest = ["elliptic-curve/digest", "ecdsa-core/digest"]
4040
ecdh = ["elliptic-curve/ecdh", "zeroize"]
4141
ecdsa = ["arithmetic", "digest", "ecdsa-core/sign", "ecdsa-core/verify", "zeroize"]
42-
endomorphism-mul = ["arithmetic"]
42+
endomorphism-mul = [] # TODO(tarcieri): remove before v0.6 release
4343
expose-field = ["arithmetic"]
4444
field-montgomery = []
4545
force-32-bit = []

k256/src/arithmetic/mul.rs

Lines changed: 65 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,68 @@
1+
//! From libsecp256k1:
2+
//!
3+
//! The Secp256k1 curve has an endomorphism, where lambda * (x, y) = (beta * x, y), where
4+
//! lambda is {0x53,0x63,0xad,0x4c,0xc0,0x5c,0x30,0xe0,0xa5,0x26,0x1c,0x02,0x88,0x12,0x64,0x5a,
5+
//! 0x12,0x2e,0x22,0xea,0x20,0x81,0x66,0x78,0xdf,0x02,0x96,0x7c,0x1b,0x23,0xbd,0x72}
6+
//!
7+
//! "Guide to Elliptic Curve Cryptography" (Hankerson, Menezes, Vanstone) gives an algorithm
8+
//! (algorithm 3.74) to find k1 and k2 given k, such that k1 + k2 * lambda == k mod n, and k1
9+
//! and k2 have a small size.
10+
//! It relies on constants a1, b1, a2, b2. These constants for the value of lambda above are:
11+
//!
12+
//! - a1 = {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15}
13+
//! - b1 = -{0xe4,0x43,0x7e,0xd6,0x01,0x0e,0x88,0x28,0x6f,0x54,0x7f,0xa9,0x0a,0xbf,0xe4,0xc3}
14+
//! - a2 = {0x01,0x14,0xca,0x50,0xf7,0xa8,0xe2,0xf3,0xf6,0x57,0xc1,0x10,0x8d,0x9d,0x44,0xcf,0xd8}
15+
//! - b2 = {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15}
16+
//!
17+
//! The algorithm then computes c1 = round(b1 * k / n) and c2 = round(b2 * k / n), and gives
18+
//! k1 = k - (c1*a1 + c2*a2) and k2 = -(c1*b1 + c2*b2). Instead, we use modular arithmetic, and
19+
//! compute k1 as k - k2 * lambda, avoiding the need for constants a1 and a2.
20+
//!
21+
//! g1, g2 are precomputed constants used to replace division with a rounded multiplication
22+
//! when decomposing the scalar for an endomorphism-based point multiplication.
23+
//!
24+
//! The possibility of using precomputed estimates is mentioned in "Guide to Elliptic Curve
25+
//! Cryptography" (Hankerson, Menezes, Vanstone) in section 3.5.
26+
//!
27+
//! The derivation is described in the paper "Efficient Software Implementation of Public-Key
28+
//! Cryptography on Sensor Networks Using the MSP430X Microcontroller" (Gouvea, Oliveira, Lopez),
29+
//! Section 4.3 (here we use a somewhat higher-precision estimate):
30+
//! d = a1*b2 - b1*a2
31+
//! g1 = round((2^272)*b2/d)
32+
//! g2 = round((2^272)*b1/d)
33+
//!
34+
//! (Note that 'd' is also equal to the curve order here because [a1,b1] and [a2,b2] are found
35+
//! as outputs of the Extended Euclidean Algorithm on inputs 'order' and 'lambda').
36+
//!
37+
//! @fjarri:
38+
//!
39+
//! To be precise, the method used here is based on
40+
//! "An Alternate Decomposition of an Integer for Faster Point Multiplication on Certain Elliptic Curves"
41+
//! by Young-Ho Park, Sangtae Jeong, Chang Han Kim, and Jongin Lim
42+
//! (https://link.springer.com/chapter/10.1007%2F3-540-45664-3_23)
43+
//!
44+
//! The precision used for `g1` and `g2` is not enough to ensure correct approximation at all times.
45+
//! For example, `2^272 * b1 / n` used to calculate `g2` is rounded down.
46+
//! This means that the approximation `z' = k * g2 / 2^272` always slightly underestimates
47+
//! the real value `z = b1 * k / n`. Therefore, when the fractional part of `z` is just slightly above
48+
//! 0.5, it will be rounded up, but `z'` will have the fractional part slightly below 0.5 and will be
49+
//! rounded down.
50+
//!
51+
//! The difference `z - z' = k * delta / 2^272`, where `delta = b1 * 2^272 mod n`.
52+
//! The closest `z` can get to the fractional part equal to .5 is `1 / (2n)` (since `n` is odd).
53+
//! Therefore, to guarantee that `z'` will always be rounded to the same value, one must have
54+
//! `delta / 2^m < 1 / (2n * (n - 1))`, where `m` is the power of 2 used for the approximation.
55+
//! This means that one should use at least `m = 512` (since `0 < delta < 1`).
56+
//! Indeed, tests show that with only `m = 272` the approximation produces off-by-1 errors occasionally.
57+
//!
58+
//! Now since `r1` is calculated as `k - r2 * lambda mod n`, the contract `r1 + r2 * lambda = k mod n`
59+
//! is always satisfied. The method guarantees both `r1` and `r2` to be less than `sqrt(n)`
60+
//! (so, fit in 128 bits) if the rounding is applied correctly - but in our case the off-by-1 errors
61+
//! will produce different `r1` and `r2` which are not necessarily bounded by `sqrt(n)`.
62+
//!
63+
//! In experiments, I was not able to detect any case where they would go outside the 128 bit bound,
64+
//! but I cannot be sure that it cannot happen.
65+
166
use crate::arithmetic::{scalar::Scalar, ProjectivePoint};
267
use core::ops::{Mul, MulAssign};
368
use elliptic_curve::subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
@@ -41,146 +106,32 @@ impl LookupTable {
41106
}
42107
}
43108

44-
/// Returns `[a_0, ..., a_64]` such that `sum(a_j * 2^(j * 4)) == x`,
45-
/// and `-8 <= a_j <= 7`.
46-
#[cfg(not(feature = "endomorphism-mul"))]
47-
fn to_radix_16(x: &Scalar) -> [i8; 65] {
48-
// `x` can have up to 256 bits, so we need an additional byte to store the carry.
49-
let mut output = [0i8; 65];
50-
51-
// Step 1: change radix.
52-
// Convert from radix 256 (bytes) to radix 16 (nibbles)
53-
let bytes = x.to_bytes();
54-
for i in 0..32 {
55-
output[2 * i] = (bytes[31 - i] & 0xf) as i8;
56-
output[2 * i + 1] = ((bytes[31 - i] >> 4) & 0xf) as i8;
57-
}
58-
59-
// Step 2: recenter coefficients from [0,16) to [-8,8)
60-
for i in 0..64 {
61-
let carry = (output[i] + 8) >> 4;
62-
output[i] -= carry << 4;
63-
output[i + 1] += carry;
64-
}
65-
66-
output
67-
}
68-
69-
#[cfg(not(feature = "endomorphism-mul"))]
70-
fn mul_windowed(x: &ProjectivePoint, k: &Scalar) -> ProjectivePoint {
71-
let scalar_digits = to_radix_16(k);
72-
let lookup_table = LookupTable::from(x);
73-
let mut acc = lookup_table.select(scalar_digits[64]);
74-
for i in (0..64).rev() {
75-
for _j in 0..4 {
76-
acc = acc.double();
77-
}
78-
acc += &lookup_table.select(scalar_digits[i]);
79-
}
80-
acc
81-
}
82-
83-
/*
84-
From libsecp256k1:
85-
86-
The Secp256k1 curve has an endomorphism, where lambda * (x, y) = (beta * x, y), where
87-
lambda is {0x53,0x63,0xad,0x4c,0xc0,0x5c,0x30,0xe0,0xa5,0x26,0x1c,0x02,0x88,0x12,0x64,0x5a,
88-
0x12,0x2e,0x22,0xea,0x20,0x81,0x66,0x78,0xdf,0x02,0x96,0x7c,0x1b,0x23,0xbd,0x72}
89-
90-
"Guide to Elliptic Curve Cryptography" (Hankerson, Menezes, Vanstone) gives an algorithm
91-
(algorithm 3.74) to find k1 and k2 given k, such that k1 + k2 * lambda == k mod n, and k1
92-
and k2 have a small size.
93-
It relies on constants a1, b1, a2, b2. These constants for the value of lambda above are:
94-
95-
- a1 = {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15}
96-
- b1 = -{0xe4,0x43,0x7e,0xd6,0x01,0x0e,0x88,0x28,0x6f,0x54,0x7f,0xa9,0x0a,0xbf,0xe4,0xc3}
97-
- a2 = {0x01,0x14,0xca,0x50,0xf7,0xa8,0xe2,0xf3,0xf6,0x57,0xc1,0x10,0x8d,0x9d,0x44,0xcf,0xd8}
98-
- b2 = {0x30,0x86,0xd2,0x21,0xa7,0xd4,0x6b,0xcd,0xe8,0x6c,0x90,0xe4,0x92,0x84,0xeb,0x15}
99-
100-
The algorithm then computes c1 = round(b1 * k / n) and c2 = round(b2 * k / n), and gives
101-
k1 = k - (c1*a1 + c2*a2) and k2 = -(c1*b1 + c2*b2). Instead, we use modular arithmetic, and
102-
compute k1 as k - k2 * lambda, avoiding the need for constants a1 and a2.
103-
104-
g1, g2 are precomputed constants used to replace division with a rounded multiplication
105-
when decomposing the scalar for an endomorphism-based point multiplication.
106-
107-
The possibility of using precomputed estimates is mentioned in "Guide to Elliptic Curve
108-
Cryptography" (Hankerson, Menezes, Vanstone) in section 3.5.
109-
110-
The derivation is described in the paper "Efficient Software Implementation of Public-Key
111-
Cryptography on Sensor Networks Using the MSP430X Microcontroller" (Gouvea, Oliveira, Lopez),
112-
Section 4.3 (here we use a somewhat higher-precision estimate):
113-
d = a1*b2 - b1*a2
114-
g1 = round((2^272)*b2/d)
115-
g2 = round((2^272)*b1/d)
116-
117-
(Note that 'd' is also equal to the curve order here because [a1,b1] and [a2,b2] are found
118-
as outputs of the Extended Euclidean Algorithm on inputs 'order' and 'lambda').
119-
*/
120-
121-
/*
122-
@fjarri:
123-
124-
To be precise, the method used here is based on
125-
"An Alternate Decomposition of an Integer for Faster Point Multiplication on Certain Elliptic Curves"
126-
by Young-Ho Park, Sangtae Jeong, Chang Han Kim, and Jongin Lim
127-
(https://link.springer.com/chapter/10.1007%2F3-540-45664-3_23)
128-
129-
The precision used for `g1` and `g2` is not enough to ensure correct approximation at all times.
130-
For example, `2^272 * b1 / n` used to calculate `g2` is rounded down.
131-
This means that the approximation `z' = k * g2 / 2^272` always slightly underestimates
132-
the real value `z = b1 * k / n`. Therefore, when the fractional part of `z` is just slightly above
133-
0.5, it will be rounded up, but `z'` will have the fractional part slightly below 0.5 and will be
134-
rounded down.
135-
136-
The difference `z - z' = k * delta / 2^272`, where `delta = b1 * 2^272 mod n`.
137-
The closest `z` can get to the fractional part equal to .5 is `1 / (2n)` (since `n` is odd).
138-
Therefore, to guarantee that `z'` will always be rounded to the same value, one must have
139-
`delta / 2^m < 1 / (2n * (n - 1))`, where `m` is the power of 2 used for the approximation.
140-
This means that one should use at least `m = 512` (since `0 < delta < 1`).
141-
Indeed, tests show that with only `m = 272` the approximation produces off-by-1 errors occasionally.
142-
143-
Now since `r1` is calculated as `k - r2 * lambda mod n`, the contract `r1 + r2 * lambda = k mod n`
144-
is always satisfied. The method guarantees both `r1` and `r2` to be less than `sqrt(n)`
145-
(so, fit in 128 bits) if the rounding is applied correctly - but in our case the off-by-1 errors
146-
will produce different `r1` and `r2` which are not necessarily bounded by `sqrt(n)`.
147-
148-
In experiments, I was not able to detect any case where they would go outside the 128 bit bound,
149-
but I cannot be sure that it cannot happen.
150-
*/
151-
152-
#[cfg(feature = "endomorphism-mul")]
153109
const MINUS_LAMBDA: Scalar = Scalar::from_bytes_unchecked(&[
154110
0xac, 0x9c, 0x52, 0xb3, 0x3f, 0xa3, 0xcf, 0x1f, 0x5a, 0xd9, 0xe3, 0xfd, 0x77, 0xed, 0x9b, 0xa4,
155111
0xa8, 0x80, 0xb9, 0xfc, 0x8e, 0xc7, 0x39, 0xc2, 0xe0, 0xcf, 0xc8, 0x10, 0xb5, 0x12, 0x83, 0xcf,
156112
]);
157113

158-
#[cfg(feature = "endomorphism-mul")]
159114
const MINUS_B1: Scalar = Scalar::from_bytes_unchecked(&[
160115
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
161116
0xe4, 0x43, 0x7e, 0xd6, 0x01, 0x0e, 0x88, 0x28, 0x6f, 0x54, 0x7f, 0xa9, 0x0a, 0xbf, 0xe4, 0xc3,
162117
]);
163118

164-
#[cfg(feature = "endomorphism-mul")]
165119
const MINUS_B2: Scalar = Scalar::from_bytes_unchecked(&[
166120
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
167121
0x8a, 0x28, 0x0a, 0xc5, 0x07, 0x74, 0x34, 0x6d, 0xd7, 0x65, 0xcd, 0xa8, 0x3d, 0xb1, 0x56, 0x2c,
168122
]);
169123

170-
#[cfg(feature = "endomorphism-mul")]
171124
const G1: Scalar = Scalar::from_bytes_unchecked(&[
172125
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x86,
173126
0xd2, 0x21, 0xa7, 0xd4, 0x6b, 0xcd, 0xe8, 0x6c, 0x90, 0xe4, 0x92, 0x84, 0xeb, 0x15, 0x3d, 0xab,
174127
]);
175128

176-
#[cfg(feature = "endomorphism-mul")]
177129
const G2: Scalar = Scalar::from_bytes_unchecked(&[
178130
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x43,
179131
0x7e, 0xd6, 0x01, 0x0e, 0x88, 0x28, 0x6f, 0x54, 0x7f, 0xa9, 0x0a, 0xbf, 0xe4, 0xc4, 0x22, 0x12,
180132
]);
181133

182134
/// Find r1 and r2 given k, such that r1 + r2 * lambda == k mod n.
183-
#[cfg(feature = "endomorphism-mul")]
184135
fn decompose_scalar(k: &Scalar) -> (Scalar, Scalar) {
185136
// these _var calls are constant time since the shift amount is constant
186137
let c1 = k.mul_shift_var(&G1, 272);
@@ -197,7 +148,6 @@ fn decompose_scalar(k: &Scalar) -> (Scalar, Scalar) {
197148
/// Returns `[a_0, ..., a_32]` such that `sum(a_j * 2^(j * 4)) == x`,
198149
/// and `-8 <= a_j <= 7`.
199150
/// Assumes `x < 2^128`.
200-
#[cfg(feature = "endomorphism-mul")]
201151
fn to_radix_16_half(x: &Scalar) -> [i8; 33] {
202152
// `x` can have up to 256 bits, so we need an additional byte to store the carry.
203153
let mut output = [0i8; 33];
@@ -222,7 +172,6 @@ fn to_radix_16_half(x: &Scalar) -> [i8; 33] {
222172
output
223173
}
224174

225-
#[cfg(feature = "endomorphism-mul")]
226175
fn mul_windowed(x: &ProjectivePoint, k: &Scalar) -> ProjectivePoint {
227176
let (r1, r2) = decompose_scalar(k);
228177
let x_beta = x.endomorphism();

k256/src/arithmetic/projective.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use elliptic_curve::{
1616
};
1717

1818
#[rustfmt::skip]
19-
#[cfg(feature = "endomorphism-mul")]
2019
const ENDOMORPHISM_BETA: FieldElement = FieldElement::from_bytes_unchecked(&[
2120
0x7a, 0xe9, 0x6a, 0x2b, 0x65, 0x7c, 0x07, 0x10,
2221
0x6e, 0x64, 0x47, 0x9e, 0xac, 0x34, 0x34, 0xe9,
@@ -239,7 +238,6 @@ impl ProjectivePoint {
239238
}
240239

241240
/// Calculates SECP256k1 endomorphism: `self * lambda`.
242-
#[cfg(feature = "endomorphism-mul")]
243241
pub fn endomorphism(&self) -> Self {
244242
Self {
245243
x: self.x * &ENDOMORPHISM_BETA,

k256/src/arithmetic/scalar.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ impl Scalar {
179179

180180
/// Attempts to parse the given byte array as a scalar.
181181
/// Does not check the result for being in the correct range.
182-
#[cfg(feature = "endomorphism-mul")]
183182
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
184183
Self(ScalarImpl::from_bytes_unchecked(bytes))
185184
}
@@ -659,15 +658,13 @@ mod tests {
659658
}
660659

661660
proptest! {
662-
663661
#[test]
664662
fn fuzzy_roundtrip_to_bytes(a in scalar()) {
665663
let a_back = Scalar::from_repr(a.to_bytes()).unwrap();
666664
assert_eq!(a, a_back);
667665
}
668666

669667
#[test]
670-
#[cfg(feature = "endomorphism-mul")]
671668
fn fuzzy_roundtrip_to_bytes_unchecked(a in scalar()) {
672669
let bytes = a.to_bytes();
673670
let a_back = Scalar::from_bytes_unchecked(bytes.as_ref());

k256/src/arithmetic/scalar/scalar_4x64.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ impl Scalar4x64 {
158158
self.0[0] as u32
159159
}
160160

161-
#[cfg(feature = "endomorphism-mul")]
162161
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
163162
// Interpret the bytes as a big-endian integer w.
164163
let w3 =

k256/src/arithmetic/scalar/scalar_8x32.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ impl Scalar8x32 {
187187
self.0[0]
188188
}
189189

190-
#[cfg(feature = "endomorphism-mul")]
191190
pub(crate) const fn from_bytes_unchecked(bytes: &[u8; 32]) -> Self {
192191
// Interpret the bytes as a big-endian integer w.
193192
let w7 =

0 commit comments

Comments
 (0)