Skip to content

Commit 83ff0b0

Browse files
committed
crypto/elliptic: use generics for nistec-based curves
There was no way to use an interface because the methods on the Point types return concrete Point values, as they should. A couple somewhat minor annoyances: - Allocations went up due to #48849. This is fine here, where math/big causes allocations anyway, but would probably not be fine in nistec itself. - Carrying the newPoint/newGenerator functions around as a field is a little weird, even if type-safe. It also means we have to make what were functions methods so they can access newPoint to return the zero value. This is #35966. For #52182 Change-Id: I050f3a27f15d3f189818da80da9de0cba0548931 Reviewed-on: https://go-review.googlesource.com/c/go/+/360015 Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Filippo Valsorda <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Russ Cox <[email protected]>
1 parent 6796a79 commit 83ff0b0

File tree

4 files changed

+219
-448
lines changed

4 files changed

+219
-448
lines changed

src/crypto/elliptic/nistec.go

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright 2013 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+
package elliptic
6+
7+
import (
8+
"crypto/elliptic/internal/nistec"
9+
"crypto/rand"
10+
"math/big"
11+
)
12+
13+
var p224 = &nistCurve[*nistec.P224Point]{
14+
newPoint: nistec.NewP224Point,
15+
newGenerator: nistec.NewP224Generator,
16+
}
17+
18+
func initP224() {
19+
p224.params = &CurveParams{
20+
Name: "P-224",
21+
BitSize: 224,
22+
// FIPS 186-4, section D.1.2.2
23+
P: bigFromDecimal("26959946667150639794667015087019630673557916260026308143510066298881"),
24+
N: bigFromDecimal("26959946667150639794667015087019625940457807714424391721682722368061"),
25+
B: bigFromHex("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"),
26+
Gx: bigFromHex("b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"),
27+
Gy: bigFromHex("bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34"),
28+
}
29+
}
30+
31+
var p384 = &nistCurve[*nistec.P384Point]{
32+
newPoint: nistec.NewP384Point,
33+
newGenerator: nistec.NewP384Generator,
34+
}
35+
36+
func initP384() {
37+
p384.params = &CurveParams{
38+
Name: "P-384",
39+
BitSize: 384,
40+
// FIPS 186-4, section D.1.2.4
41+
P: bigFromDecimal("394020061963944792122790401001436138050797392704654" +
42+
"46667948293404245721771496870329047266088258938001861606973112319"),
43+
N: bigFromDecimal("394020061963944792122790401001436138050797392704654" +
44+
"46667946905279627659399113263569398956308152294913554433653942643"),
45+
B: bigFromHex("b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088" +
46+
"f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"),
47+
Gx: bigFromHex("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741" +
48+
"e082542a385502f25dbf55296c3a545e3872760ab7"),
49+
Gy: bigFromHex("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da31" +
50+
"13b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"),
51+
}
52+
}
53+
54+
var p521 = &nistCurve[*nistec.P521Point]{
55+
newPoint: nistec.NewP521Point,
56+
newGenerator: nistec.NewP521Generator,
57+
}
58+
59+
func initP521() {
60+
p521.params = &CurveParams{
61+
Name: "P-521",
62+
BitSize: 521,
63+
// FIPS 186-4, section D.1.2.5
64+
P: bigFromDecimal("68647976601306097149819007990813932172694353001433" +
65+
"0540939446345918554318339765605212255964066145455497729631139148" +
66+
"0858037121987999716643812574028291115057151"),
67+
N: bigFromDecimal("68647976601306097149819007990813932172694353001433" +
68+
"0540939446345918554318339765539424505774633321719753296399637136" +
69+
"3321113864768612440380340372808892707005449"),
70+
B: bigFromHex("0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8" +
71+
"b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef" +
72+
"451fd46b503f00"),
73+
Gx: bigFromHex("00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f8" +
74+
"28af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf9" +
75+
"7e7e31c2e5bd66"),
76+
Gy: bigFromHex("011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817" +
77+
"afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088" +
78+
"be94769fd16650"),
79+
}
80+
}
81+
82+
// nistCurve is a Curve implementation based on a nistec Point.
83+
//
84+
// It's a wrapper that exposes the big.Int-based Curve interface and encodes the
85+
// legacy idiosyncrasies it requires, such as invalid and infinity point
86+
// handling.
87+
//
88+
// To interact with the nistec package, points are encoded into and decoded from
89+
// properly formatted byte slices. All big.Int use is limited to this package.
90+
// Encoding and decoding is 1/1000th of the runtime of a scalar multiplication,
91+
// so the overhead is acceptable.
92+
type nistCurve[Point nistPoint[Point]] struct {
93+
newPoint func() Point
94+
newGenerator func() Point
95+
params *CurveParams
96+
}
97+
98+
// nistPoint is a generic constraint for the nistec Point types.
99+
type nistPoint[T any] interface {
100+
Bytes() []byte
101+
SetBytes([]byte) (T, error)
102+
Add(T, T) T
103+
Double(T) T
104+
ScalarMult(T, []byte) T
105+
}
106+
107+
func (curve *nistCurve[Point]) Params() *CurveParams {
108+
return curve.params
109+
}
110+
111+
func (curve *nistCurve[Point]) IsOnCurve(x, y *big.Int) bool {
112+
// IsOnCurve is documented to reject (0, 0), the conventional point at
113+
// infinity, which however is accepted by pointFromAffine.
114+
if x.Sign() == 0 && y.Sign() == 0 {
115+
return false
116+
}
117+
_, ok := curve.pointFromAffine(x, y)
118+
return ok
119+
}
120+
121+
func (curve *nistCurve[Point]) pointFromAffine(x, y *big.Int) (p Point, ok bool) {
122+
// (0, 0) is by convention the point at infinity, which can't be represented
123+
// in affine coordinates. Marshal incorrectly encodes it as an uncompressed
124+
// point, which SetBytes would correctly reject. See Issue 37294.
125+
if x.Sign() == 0 && y.Sign() == 0 {
126+
return curve.newPoint(), true
127+
}
128+
if x.Sign() < 0 || y.Sign() < 0 {
129+
return curve.newPoint(), false
130+
}
131+
if x.BitLen() > curve.params.BitSize || y.BitLen() > curve.params.BitSize {
132+
return *new(Point), false
133+
}
134+
p, err := curve.newPoint().SetBytes(Marshal(curve, x, y))
135+
if err != nil {
136+
return *new(Point), false
137+
}
138+
return p, true
139+
}
140+
141+
func (curve *nistCurve[Point]) pointToAffine(p Point) (x, y *big.Int) {
142+
out := p.Bytes()
143+
if len(out) == 1 && out[0] == 0 {
144+
// This is the correct encoding of the point at infinity, which
145+
// Unmarshal does not support. See Issue 37294.
146+
return new(big.Int), new(big.Int)
147+
}
148+
x, y = Unmarshal(curve, out)
149+
if x == nil {
150+
panic("crypto/elliptic: internal error: Unmarshal rejected a valid point encoding")
151+
}
152+
return x, y
153+
}
154+
155+
// randomPoint returns a random point on the curve. It's used when Add,
156+
// Double, or ScalarMult are fed a point not on the curve, which is undefined
157+
// behavior. Originally, we used to do the math on it anyway (which allows
158+
// invalid curve attacks) and relied on the caller and Unmarshal to avoid this
159+
// happening in the first place. Now, we just can't construct a nistec Point
160+
// for an invalid pair of coordinates, because that API is safer. If we panic,
161+
// we risk introducing a DoS. If we return nil, we risk a panic. If we return
162+
// the input, ecdsa.Verify might fail open. The safest course seems to be to
163+
// return a valid, random point, which hopefully won't help the attacker.
164+
func (curve *nistCurve[Point]) randomPoint() (x, y *big.Int) {
165+
_, x, y, err := GenerateKey(curve, rand.Reader)
166+
if err != nil {
167+
panic("crypto/elliptic: failed to generate random point")
168+
}
169+
return x, y
170+
}
171+
172+
func (curve *nistCurve[Point]) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
173+
p1, ok := curve.pointFromAffine(x1, y1)
174+
if !ok {
175+
return curve.randomPoint()
176+
}
177+
p2, ok := curve.pointFromAffine(x2, y2)
178+
if !ok {
179+
return curve.randomPoint()
180+
}
181+
return curve.pointToAffine(p1.Add(p1, p2))
182+
}
183+
184+
func (curve *nistCurve[Point]) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
185+
p, ok := curve.pointFromAffine(x1, y1)
186+
if !ok {
187+
return curve.randomPoint()
188+
}
189+
return curve.pointToAffine(p.Double(p))
190+
}
191+
192+
func (curve *nistCurve[Point]) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int, *big.Int) {
193+
p, ok := curve.pointFromAffine(Bx, By)
194+
if !ok {
195+
return curve.randomPoint()
196+
}
197+
return curve.pointToAffine(p.ScalarMult(p, scalar))
198+
}
199+
200+
func (curve *nistCurve[Point]) ScalarBaseMult(scalar []byte) (*big.Int, *big.Int) {
201+
p := curve.newGenerator()
202+
return curve.pointToAffine(p.ScalarMult(p, scalar))
203+
}
204+
205+
func bigFromDecimal(s string) *big.Int {
206+
b, ok := new(big.Int).SetString(s, 10)
207+
if !ok {
208+
panic("invalid encoding")
209+
}
210+
return b
211+
}
212+
213+
func bigFromHex(s string) *big.Int {
214+
b, ok := new(big.Int).SetString(s, 16)
215+
if !ok {
216+
panic("invalid encoding")
217+
}
218+
return b
219+
}

src/crypto/elliptic/p224.go

-139
This file was deleted.

0 commit comments

Comments
 (0)