Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 62 additions & 27 deletions dcrec/secp256k1/curve.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2024 The Decred developers
// Copyright (c) 2015-2026 The Decred developers
// Copyright 2013-2014 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
Expand Down Expand Up @@ -26,27 +26,49 @@ import (
// (x, y) position on the curve, the Jacobian coordinates are (x1, y1, z1)
// where x = x1/z1^2 and y = y1/z1^3.

// hexToFieldVal converts the passed hex string into a FieldVal and will panic
// if there is an error. This is only provided for the hard-coded constants so
// errors in the source code can be detected. It will only (and must only) be
// called with hard-coded values.
func hexToFieldVal(s string) *FieldVal {
// mustFieldValInternal converts the passed hex string into a [FieldVal] and
// will panic if there is an error. Values that overflow are treated as an
// error unless the allow overflow flag is set.
//
// This is only provided for the hard-coded constants so errors in the source
// code can be detected. It will only (and must only) be called with hard-coded
// values.
func mustFieldValInternal(s string, allowOverflow bool) *FieldVal {
if len(s)%2 != 0 {
s = "0" + s
}
b, err := hex.DecodeString(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
if len(b) > 32 {
panic("hex in source file overflows uint256: " + s)
}
var f FieldVal
if overflow := f.SetByteSlice(b); overflow {
panic("hex in source file overflows mod P: " + s)
if overflow := f.SetByteSlice(b); overflow && !allowOverflow {
panic("hex in source file overflows mod N scalar: " + s)
}
return &f
}

// hexToModNScalar converts the passed hex string into a ModNScalar and will
// panic if there is an error. This is only provided for the hard-coded
// constants so errors in the source code can be detected. It will only (and
// must only) be called with hard-coded values.
func hexToModNScalar(s string) *ModNScalar {
// mustFieldVal converts the passed hex string into a [FieldVal] and will panic
// if there is an error. Values that overflow are treated as an error.
//
// This is only provided for the hard-coded constants so errors in the source
// code can be detected. It will only (and must only) be called with hard-coded
// values.
func mustFieldVal(s string) *FieldVal {
return mustFieldValInternal(s, false)
}

// mustModNScalarInternal converts the passed hex string into a [ModNScalar] and
// will panic if there is an error. Values that overflow are treated as an
// error unless the allow overflow flag is set.
//
// This is only provided for the hard-coded constants so errors in the source
// code can be detected. It will only (and must only) be called with hard-coded
// values.
func mustModNScalarInternal(s string, allowOverflow bool) *ModNScalar {
var isNegative bool
if len(s) > 0 && s[0] == '-' {
isNegative = true
Expand All @@ -59,8 +81,11 @@ func hexToModNScalar(s string) *ModNScalar {
if err != nil {
panic("invalid hex in source file: " + s)
}
if len(b) > 32 {
panic("hex in source file overflows uint256: " + s)
}
var scalar ModNScalar
if overflow := scalar.SetByteSlice(b); overflow {
if overflow := scalar.SetByteSlice(b); overflow && !allowOverflow {
panic("hex in source file overflows mod N scalar: " + s)
}
if isNegative {
Expand All @@ -69,6 +94,16 @@ func hexToModNScalar(s string) *ModNScalar {
return &scalar
}

// mustModNScalar converts the passed hex string into a [ModNScalar] and will
// panic if there is an error. Values that overflow are treated as an error.
//
// This is only provided for the hard-coded constants so errors in the source
// code can be detected. It will only (and must only) be called with hard-coded
// values.
func mustModNScalar(s string) *ModNScalar {
return mustModNScalarInternal(s, false)
}

var (
// The following constants are used to accelerate scalar point
// multiplication through the use of the endomorphism:
Expand All @@ -80,21 +115,21 @@ var (
//
// Additionally, see the scalar multiplication function in this file for
// details on how they are used.
endoNegLambda = hexToModNScalar("-5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72")
endoBeta = hexToFieldVal("7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee")
endoNegB1 = hexToModNScalar("e4437ed6010e88286f547fa90abfe4c3")
endoNegB2 = hexToModNScalar("-3086d221a7d46bcde86c90e49284eb15")
endoZ1 = hexToModNScalar("3086d221a7d46bcde86c90e49284eb153daa8a1471e8ca7f")
endoZ2 = hexToModNScalar("e4437ed6010e88286f547fa90abfe4c4221208ac9df506c6")
endoNegLambda = mustModNScalar("-5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72")
endoBeta = mustFieldVal("7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee")
endoNegB1 = mustModNScalar("e4437ed6010e88286f547fa90abfe4c3")
endoNegB2 = mustModNScalar("-3086d221a7d46bcde86c90e49284eb15")
endoZ1 = mustModNScalar("3086d221a7d46bcde86c90e49284eb153daa8a1471e8ca7f")
endoZ2 = mustModNScalar("e4437ed6010e88286f547fa90abfe4c4221208ac9df506c6")

// Alternatively, the following parameters are valid as well, however,
// benchmarks show them to be about 2% slower in practice.
// endoNegLambda = hexToModNScalar("-ac9c52b33fa3cf1f5ad9e3fd77ed9ba4a880b9fc8ec739c2e0cfc810b51283ce")
// endoBeta = hexToFieldVal("851695d49a83f8ef919bb86153cbcb16630fb68aed0a766a3ec693d68e6afa40")
// endoNegB1 = hexToModNScalar("3086d221a7d46bcde86c90e49284eb15")
// endoNegB2 = hexToModNScalar("-114ca50f7a8e2f3f657c1108d9d44cfd8")
// endoZ1 = hexToModNScalar("114ca50f7a8e2f3f657c1108d9d44cfd95fbc92c10fddd145")
// endoZ2 = hexToModNScalar("3086d221a7d46bcde86c90e49284eb153daa8a1471e8ca7f")
// endoNegLambda = mustModNScalar("-ac9c52b33fa3cf1f5ad9e3fd77ed9ba4a880b9fc8ec739c2e0cfc810b51283ce")
// endoBeta = mustFieldVal("851695d49a83f8ef919bb86153cbcb16630fb68aed0a766a3ec693d68e6afa40")
// endoNegB1 = mustModNScalar("3086d221a7d46bcde86c90e49284eb15")
// endoNegB2 = mustModNScalar("-114ca50f7a8e2f3f657c1108d9d44cfd8")
// endoZ1 = mustModNScalar("114ca50f7a8e2f3f657c1108d9d44cfd95fbc92c10fddd145")
// endoZ2 = mustModNScalar("3086d221a7d46bcde86c90e49284eb153daa8a1471e8ca7f")
)

// JacobianPoint is an element of the group formed by the secp256k1 curve in
Expand Down Expand Up @@ -1246,7 +1281,7 @@ var jacobianG = func() JacobianPoint {
return G
}()

// scalarBaseMultNonConstSlow computes k*G through ScalarMultNonConst.
// scalarBaseMultNonConstSlow computes k*G through [ScalarMultNonConst].
func scalarBaseMultNonConstSlow(k *ModNScalar, result *JacobianPoint) {
ScalarMultNonConst(k, &jacobianG, result)
}
Expand Down
28 changes: 15 additions & 13 deletions dcrec/secp256k1/curve_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ func BenchmarkAddNonConst(b *testing.B) {
// function with Z values other than one so the optimizations associated with
// Z=1 aren't used.
func BenchmarkAddNonConstNotZOne(b *testing.B) {
x1 := new(FieldVal).SetHex("d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718")
y1 := new(FieldVal).SetHex("5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190")
z1 := new(FieldVal).SetHex("2")
x2 := new(FieldVal).SetHex("91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4")
y2 := new(FieldVal).SetHex("03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1")
z2 := new(FieldVal).SetHex("3")
p1 := MakeJacobianPoint(x1, y1, z1)
p2 := MakeJacobianPoint(x2, y2, z2)
p1 := jacobianPointFromHex(
"d3e5183c393c20e4f464acf144ce9ae8266a82b67f553af33eb37e88e7fd2718",
"5b8f54deb987ec491fb692d3d48f3eebb9454b034365ad480dda0cf079651190",
"2",
)
p2 := jacobianPointFromHex(
"91abba6a34b7481d922a4bd6a04899d5a686f6cf6da4e66a0cb427fb25c04bd4",
"03fede65e30b4e7576a2abefc963ddbf9fdccbf791b77c29beadefe49951f7d1",
"3",
)

b.ReportAllocs()
b.ResetTimer()
Expand All @@ -55,7 +57,7 @@ func BenchmarkAddNonConstNotZOne(b *testing.B) {
// BenchmarkScalarBaseMultNonConst benchmarks multiplying a scalar by the base
// point of the curve using whichever variant is active.
func BenchmarkScalarBaseMultNonConst(b *testing.B) {
k := hexToModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
k := mustModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")

b.ReportAllocs()
b.ResetTimer()
Expand All @@ -68,7 +70,7 @@ func BenchmarkScalarBaseMultNonConst(b *testing.B) {
// BenchmarkScalarBaseMultNonConstFast benchmarks multiplying a scalar by the
// base point of the curve using the fast variant.
func BenchmarkScalarBaseMultNonConstFast(b *testing.B) {
k := hexToModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
k := mustModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")

b.ReportAllocs()
b.ResetTimer()
Expand All @@ -81,7 +83,7 @@ func BenchmarkScalarBaseMultNonConstFast(b *testing.B) {
// BenchmarkScalarBaseMultNonConstSlow benchmarks multiplying a scalar by the
// base point of the curve using the resource-constrained slow variant.
func BenchmarkScalarBaseMultNonConstSlow(b *testing.B) {
k := hexToModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
k := mustModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")

b.ReportAllocs()
b.ResetTimer()
Expand All @@ -99,7 +101,7 @@ func BenchmarkSplitK(b *testing.B) {
// produced scalars.
h := "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"
negOne := new(ModNScalar).NegateVal(oneModN)
halfOrder := hexToModNScalar(h)
halfOrder := mustModNScalar(h)
halfOrderMOne := new(ModNScalar).Add2(halfOrder, negOne)
halfOrderPOne := new(ModNScalar).Add2(halfOrder, oneModN)
lambdaMOne := new(ModNScalar).Add2(endoLambda, negOne)
Expand Down Expand Up @@ -139,7 +141,7 @@ func BenchmarkSplitK(b *testing.B) {
// BenchmarkScalarMultNonConst benchmarks multiplying a scalar by an arbitrary
// point on the curve.
func BenchmarkScalarMultNonConst(b *testing.B) {
k := hexToModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
k := mustModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
point := jacobianPointFromHex(
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
Expand Down
22 changes: 13 additions & 9 deletions dcrec/secp256k1/curve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

var (
// oneModN is simply the number 1 as a mod n scalar.
oneModN = hexToModNScalar("1")
oneModN = mustModNScalar("1")

// endoLambda is the positive version of the lambda constant used in the
// endomorphism. It is stored here for convenience and to avoid recomputing
Expand Down Expand Up @@ -93,11 +93,15 @@ func randJacobian(t *testing.T, rng *rand.Rand) *JacobianPoint {
// jacobianPointFromHex decodes the passed big-endian hex strings into a
// Jacobian point with its internal fields set to the resulting values. Only
// the first 32-bytes are used.
//
// This is only provided for the hard-coded constants so errors in the source
// code can be detected. It will only (and must only) be called with hard-coded
// values.
func jacobianPointFromHex(x, y, z string) JacobianPoint {
var p JacobianPoint
p.X.SetHex(x)
p.Y.SetHex(y)
p.Z.SetHex(z)
p.X = *mustFieldVal(x)
p.Y = *mustFieldVal(y)
p.Z = *mustFieldVal(z)
return p
}

Expand Down Expand Up @@ -828,7 +832,7 @@ func TestScalarBaseMultJacobian(t *testing.T) {
// Parse test data.
want := jacobianPointFromHex(test.x1, test.y1, test.z1)
wantAffine := jacobianPointFromHex(test.x2, test.y2, "01")
k := hexToModNScalar(test.k)
k := mustModNScalar(test.k)

// Ensure the test data is using points that are actually on the curve
// (or the point at infinity).
Expand Down Expand Up @@ -941,7 +945,7 @@ func TestSplitK(t *testing.T) {
// produced scalars.
h := "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"
negOne := new(ModNScalar).NegateVal(oneModN)
halfOrder := hexToModNScalar(h)
halfOrder := mustModNScalar(h)
halfOrderMOne := new(ModNScalar).Add2(halfOrder, negOne)
halfOrderPOne := new(ModNScalar).Add2(halfOrder, oneModN)
lambdaMOne := new(ModNScalar).Add2(endoLambda, negOne)
Expand Down Expand Up @@ -1170,7 +1174,7 @@ func TestDecompressY(t *testing.T) {
// Decompress the test odd y coordinate for the given test x coordinate
// and ensure the returned validity flag matches the expected result.
var oddY FieldVal
fx := new(FieldVal).SetHex(test.x)
fx := mustFieldValWithOverflow(test.x)
valid := DecompressY(fx, true, &oddY)
if valid != test.valid {
t.Errorf("%s: unexpected valid flag -- got: %v, want: %v",
Expand All @@ -1194,15 +1198,15 @@ func TestDecompressY(t *testing.T) {
}

// Ensure the decompressed odd Y coordinate is the expected value.
wantOddY := new(FieldVal).SetHex(test.wantOddY)
wantOddY := mustFieldVal(test.wantOddY)
if !wantOddY.Equals(&oddY) {
t.Errorf("%s: mismatched odd y\ngot: %v, want: %v", test.name,
oddY, wantOddY)
continue
}

// Ensure the decompressed even Y coordinate is the expected value.
wantEvenY := new(FieldVal).SetHex(test.wantEvenY)
wantEvenY := mustFieldVal(test.wantEvenY)
if !wantEvenY.Equals(&evenY) {
t.Errorf("%s: mismatched even y\ngot: %v, want: %v", test.name,
evenY, wantEvenY)
Expand Down
16 changes: 8 additions & 8 deletions dcrec/secp256k1/doc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Copyright (c) 2015-2026 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -24,8 +24,8 @@ An overview of the features provided by this package are as follows:
- Parses uncompressed, compressed, and hybrid public keys
- Serializes uncompressed and compressed public keys
- Specialized types for performing optimized and constant time field operations
- FieldVal type for working modulo the secp256k1 field prime
- ModNScalar type for working modulo the secp256k1 group order
- [FieldVal] type for working modulo the secp256k1 field prime
- [ModNScalar] type for working modulo the secp256k1 group order
- Elliptic curve operations in Jacobian projective coordinates
- Point addition
- Point doubling
Expand All @@ -37,11 +37,11 @@ An overview of the features provided by this package are as follows:
algorithms

It also provides an implementation of the Go standard library crypto/elliptic
Curve interface via the S256 function so that it may be used with other packages
in the standard library such as crypto/tls, crypto/x509, and crypto/ecdsa.
However, in the case of ECDSA, it is highly recommended to use the ecdsa sub
package of this package instead since it is optimized specifically for secp256k1
and is significantly faster as a result.
Curve interface via the [S256] function so that it may be used with other
packages in the standard library such as crypto/tls, crypto/x509, and
crypto/ecdsa. However, in the case of ECDSA, it is highly recommended to use
the ecdsa sub package of this package instead since it is optimized specifically
for secp256k1 and is significantly faster as a result.

Although this package was primarily written for dcrd, it has intentionally been
designed so it can be used as a standalone package for any projects needing to
Expand Down
Loading