initial addition of essential crypto, encoders, workflows and LLM instructions

This commit is contained in:
2025-08-20 05:47:06 +01:00
parent f449a9d415
commit b8db587d7b
159 changed files with 36993 additions and 10 deletions

View File

@@ -0,0 +1,23 @@
Due to the presence of substantial material derived from btcec this license is
required.
However, where it differs, the changed parts are CC0 as with the rest of the
content of this repository.
ISC License
Copyright (c) 2013-2017 The btcsuite developers
Copyright (c) 2015-2020 The Decred developers
Copyright (c) 2017 The Lightning Network Developers
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,54 @@
# secp256k1
> Due to the terrible state of the btcec library, and the ethical character of
> dcred, the two main secp256k1 EC libraries in the Go language, it has become
> necessary to refactor and clean up the mess of btcec's module versioning.
>
> In addition, the code has been updated to use several new features of Go that
> were added to the language since these libraries were first created, notably the
> precomps are here directly generated as binary data instead of nasty base64
> source code.
Package secp256k1 implements optimized secp256k1 elliptic curve operations.
This package provides an optimized pure Go implementation of elliptic curve
cryptography operations over the secp256k1 curve as well as data structures and
functions for working with public and secret secp256k1 keys. See
https://www.secg.org/sec2-v2.pdf for details on the standard.
In addition, sub packages are provided to produce, verify, parse, and serialize
ECDSA signatures and EC-Schnorr-DCRv0 (a custom Schnorr-based signature scheme
specific to Decred) signatures. See the README.md files in the relevant sub
packages for more details about those aspects.
An overview of the features provided by this package are as follows:
- Secret key generation, serialization, and parsing
- Public key generation, serialization and parsing per ANSI X9.62-1998
- 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
- Elliptic curve operations in Jacobian projective coordinates
- Point addition
- Point doubling
- Scalar multiplication with an arbitrary point
- Scalar multiplication with the base point (group generator)
- Point decompression from a given x coordinate
- Nonce generation via RFC6979 with support for extra data and version
information that can be used to prevent nonce reuse between signing 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.
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
use optimized secp256k1 elliptic curve cryptography.
Finally, a comprehensive suite of tests is provided to provide a high level of
quality assurance.

View File

@@ -0,0 +1,177 @@
// Copyright 2013-2016 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"testing"
)
// BenchmarkAddNonConst benchmarks the secp256k1 curve AddNonConst function with
// Z values of 1 so that the associated optimizations are used.
func BenchmarkAddNonConst(b *testing.B) {
p1 := jacobianPointFromHex(
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
"1",
)
p2 := jacobianPointFromHex(
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
"1",
)
b.ReportAllocs()
b.ResetTimer()
var result JacobianPoint
for i := 0; i < b.N; i++ {
AddNonConst(&p1, &p2, &result)
}
}
// BenchmarkAddNonConstNotZOne benchmarks the secp256k1 curve AddNonConst
// 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)
b.ReportAllocs()
b.ResetTimer()
var result JacobianPoint
for i := 0; i < b.N; i++ {
AddNonConst(&p1, &p2, &result)
}
}
// BenchmarkScalarBaseMultNonConst benchmarks multiplying a scalar by the base
// point of the curve.
func BenchmarkScalarBaseMultNonConst(b *testing.B) {
k := hexToModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
b.ReportAllocs()
b.ResetTimer()
var result JacobianPoint
for i := 0; i < b.N; i++ {
ScalarBaseMultNonConst(k, &result)
}
}
// BenchmarkSplitK benchmarks decomposing scalars into a balanced length-two
// representation.
func BenchmarkSplitK(b *testing.B) {
// Values computed from the group half order and lambda such that they
// exercise the decomposition edge cases and maximize the bit lengths of the
// produced scalars.
h := "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0"
negOne := new(ModNScalar).NegateVal(oneModN)
halfOrder := hexToModNScalar(h)
halfOrderMOne := new(ModNScalar).Add2(halfOrder, negOne)
halfOrderPOne := new(ModNScalar).Add2(halfOrder, oneModN)
lambdaMOne := new(ModNScalar).Add2(endoLambda, negOne)
lambdaPOne := new(ModNScalar).Add2(endoLambda, oneModN)
negLambda := new(ModNScalar).NegateVal(endoLambda)
halfOrderMOneMLambda := new(ModNScalar).Add2(halfOrderMOne, negLambda)
halfOrderMLambda := new(ModNScalar).Add2(halfOrder, negLambda)
halfOrderPOneMLambda := new(ModNScalar).Add2(halfOrderPOne, negLambda)
lambdaPHalfOrder := new(ModNScalar).Add2(endoLambda, halfOrder)
lambdaPOnePHalfOrder := new(ModNScalar).Add2(lambdaPOne, halfOrder)
scalars := []*ModNScalar{
new(ModNScalar), // zero
oneModN, // one
negOne, // group order - 1 (aka -1 mod N)
halfOrderMOneMLambda, // group half order - 1 - lambda
halfOrderMLambda, // group half order - lambda
halfOrderPOneMLambda, // group half order + 1 - lambda
halfOrderMOne, // group half order - 1
halfOrder, // group half order
halfOrderPOne, // group half order + 1
lambdaMOne, // lambda - 1
endoLambda, // lambda
lambdaPOne, // lambda + 1
lambdaPHalfOrder, // lambda + group half order
lambdaPOnePHalfOrder, // lambda + 1 + group half order
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i += len(scalars) {
for j := 0; j < len(scalars); j++ {
_, _ = splitK(scalars[j])
}
}
}
// BenchmarkScalarMultNonConst benchmarks multiplying a scalar by an arbitrary
// point on the curve.
func BenchmarkScalarMultNonConst(b *testing.B) {
k := hexToModNScalar("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
point := jacobianPointFromHex(
"34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
"0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
"1",
)
b.ReportAllocs()
b.ResetTimer()
var result JacobianPoint
for i := 0; i < b.N; i++ {
ScalarMultNonConst(k, &point, &result)
}
}
// BenchmarkNAF benchmarks conversion of a positive integer into its
// non-adjacent form representation.
func BenchmarkNAF(b *testing.B) {
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
kBytes := k.Bytes()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
naf(kBytes)
}
}
// BenchmarkPubKeyDecompress benchmarks how long it takes to decompress the y
// coordinate from a given public key x coordinate.
func BenchmarkPubKeyDecompress(b *testing.B) {
// Randomly generated keypair.
// Secret key: 9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d
pubKeyX := new(FieldVal).SetHex("d2e670a19c6d753d1a6d8b20bd045df8a08fb162cf508956c31268c6d81ffdab")
b.ReportAllocs()
b.ResetTimer()
var y FieldVal
for i := 0; i < b.N; i++ {
_ = DecompressY(pubKeyX, false, &y)
}
}
// BenchmarkParsePubKeyCompressed benchmarks how long it takes to parse a
// compressed public key with an even y coordinate.
func BenchmarkParsePubKeyCompressed(b *testing.B) {
format := "02"
x := "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d"
pubKeyBytes := hexToBytes(format + x)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ParsePubKey(pubKeyBytes)
}
}
// BenchmarkParsePubKeyUncompressed benchmarks how long it takes to parse an
// uncompressed public key.
func BenchmarkParsePubKeyUncompressed(b *testing.B) {
format := "04"
x := "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"
y := "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3"
pubKeyBytes := hexToBytes(format + x + y)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ParsePubKey(pubKeyBytes)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// Package secp256k1 implements optimized secp256k1 elliptic curve operations in
// pure Go. This is an update that uses the Go 1.16 embed library instead of
// generated code for the data.
//
// This package provides an optimized pure Go implementation of elliptic curve
// cryptography operations over the secp256k1 curve as well as data structures and
// functions for working with public and secret secp256k1 keys. See
// https://www.secg.org/sec2-v2.pdf for details on the standard.
//
// In addition, sub packages are provided to produce, verify, parse, and serialize
// ECDSA signatures and EC-Schnorr-DCRv0 (a custom Schnorr-based signature scheme
// specific to Decred) signatures. See the README.md files in the relevant sub
// packages for more details about those aspects.
//
// An overview of the features provided by this package are as follows:
//
// - Secret key generation, serialization, and parsing
// - Public key generation, serialization and parsing per ANSI X9.62-1998
// - 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
// - Elliptic curve operations in Jacobian projective coordinates
// - Point addition
// - Point doubling
// - Scalar multiplication with an arbitrary point
// - Scalar multiplication with the base point (group generator)
// - Point decompression from a given x coordinate
// - Nonce generation via RFC6979 with support for extra data and version
// information that can be used to prevent nonce reuse between signing
// 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.
//
// 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
// use optimized secp256k1 elliptic curve cryptography.
//
// Finally, a comprehensive suite of tests is provided to provide a high level of
// quality assurance.
//
// # Use of secp256k1 in Decred
//
// At the time of this writing, the primary public key cryptography in widespread
// use on the Decred network used to secure coins is based on elliptic curves
// defined by the secp256k1 domain parameters.
package secp256k1

View File

@@ -0,0 +1,21 @@
// Copyright (c) 2015 The btcsuite developers
// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
// GenerateSharedSecret generates a shared secret based on a secret key and a
// public key using Diffie-Hellman key exchange (ECDH) (RFC 5903).
// RFC5903 Section 9 states we should only return x.
//
// It is recommended to securely hash the result before using as a cryptographic
// key.
func GenerateSharedSecret(seckey *SecretKey, pubkey *PublicKey) []byte {
var point, result JacobianPoint
pubkey.AsJacobian(&point)
ScalarMultNonConst(&seckey.Key, &point, &result)
result.ToAffine()
xBytes := result.X.Bytes()
return xBytes[:]
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"testing"
"next.orly.dev/pkg/utils"
)
func TestGenerateSharedSecret(t *testing.T) {
secKey1, err := GenerateSecretKey()
if err != nil {
t.Errorf("secret key generation error: %s", err)
return
}
secKey2, err := GenerateSecretKey()
if err != nil {
t.Errorf("secret key generation error: %s", err)
return
}
pubKey1 := secKey1.PubKey()
pubKey2 := secKey2.PubKey()
secret1 := GenerateSharedSecret(secKey1, pubKey2)
secret2 := GenerateSharedSecret(secKey2, pubKey1)
if !utils.FastEqual(secret1, secret2) {
t.Errorf(
"ECDH failed, secrets mismatch - first: %x, second: %x",
secret1, secret2,
)
}
}

View File

@@ -0,0 +1,247 @@
// Copyright 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
// References:
// [SECG]: Recommended Elliptic Curve Domain Parameters
// https://www.secg.org/sec2-v2.pdf
//
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
import (
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
)
// CurveParams contains the parameters for the secp256k1 curve.
type CurveParams struct {
// P is the prime used in the secp256k1 field.
P *big.Int
// N is the order of the secp256k1 curve group generated by the base point.
N *big.Int
// Gx and Gy are the x and y coordinate of the base point, respectively.
Gx, Gy *big.Int
// BitSize is the size of the underlying secp256k1 field in bits.
BitSize int
// H is the cofactor of the secp256k1 curve.
H int
// ByteSize is simply the bit size / 8 and is provided for convenience
// since it is calculated repeatedly.
ByteSize int
}
// Curve parameters taken from [SECG] section 2.4.1.
var curveParams = CurveParams{
P: fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),
N: fromHex("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),
Gx: fromHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),
Gy: fromHex("483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"),
BitSize: 256,
H: 1,
ByteSize: 256 / 8,
}
// Params returns the secp256k1 curve parameters for convenience.
func Params() *CurveParams { return &curveParams }
// KoblitzCurve provides an implementation for secp256k1 that fits the ECC Curve
// interface from crypto/elliptic.
type KoblitzCurve struct {
*elliptic.CurveParams
}
// bigAffineToJacobian takes an affine point (x, y) as big integers and converts
// it to Jacobian point with Z=1.
func bigAffineToJacobian(x, y *big.Int, result *JacobianPoint) {
result.X.SetByteSlice(x.Bytes())
result.Y.SetByteSlice(y.Bytes())
result.Z.SetInt(1)
}
// jacobianToBigAffine takes a Jacobian point (x, y, z) as field values and
// converts it to an affine point as big integers.
func jacobianToBigAffine(point *JacobianPoint) (*big.Int, *big.Int) {
point.ToAffine()
// Convert the field values for the now affine point to big.Ints.
x3, y3 := new(big.Int), new(big.Int)
x3.SetBytes(point.X.Bytes()[:])
y3.SetBytes(point.Y.Bytes()[:])
return x3, y3
}
// Params returns the parameters for the curve.
//
// This is part of the elliptic.Curve interface implementation.
func (curve *KoblitzCurve) Params() *elliptic.CurveParams {
return curve.CurveParams
}
// IsOnCurve returns whether or not the affine point (x,y) is on the curve.
//
// This is part of the elliptic.Curve interface implementation. This function
// differs from the crypto/elliptic algorithm since a = 0 not -3.
func (curve *KoblitzCurve) IsOnCurve(x, y *big.Int) bool {
// Convert big ints to a Jacobian point for faster arithmetic.
var point JacobianPoint
bigAffineToJacobian(x, y, &point)
return isOnCurve(&point.X, &point.Y)
}
// Add returns the sum of (x1,y1) and (x2,y2).
//
// This is part of the elliptic.Curve interface implementation.
func (curve *KoblitzCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
// The point at infinity is the identity according to the group law for
// elliptic curve cryptography. Thus, ∞ + P = P and P + ∞ = P.
if x1.Sign() == 0 && y1.Sign() == 0 {
return x2, y2
}
if x2.Sign() == 0 && y2.Sign() == 0 {
return x1, y1
}
// Convert the affine coordinates from big integers to Jacobian points,
// do the point addition in Jacobian projective space, and convert the
// Jacobian point back to affine big.Ints.
var p1, p2, result JacobianPoint
bigAffineToJacobian(x1, y1, &p1)
bigAffineToJacobian(x2, y2, &p2)
AddNonConst(&p1, &p2, &result)
return jacobianToBigAffine(&result)
}
// Double returns 2*(x1,y1).
//
// This is part of the elliptic.Curve interface implementation.
func (curve *KoblitzCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
if y1.Sign() == 0 {
return new(big.Int), new(big.Int)
}
// Convert the affine coordinates from big integers to Jacobian points,
// do the point doubling in Jacobian projective space, and convert the
// Jacobian point back to affine big.Ints.
var point, result JacobianPoint
bigAffineToJacobian(x1, y1, &point)
DoubleNonConst(&point, &result)
return jacobianToBigAffine(&result)
}
// moduloReduce reduces k from more than 32 bytes to 32 bytes and under. This
// is done by doing a simple modulo curve.N. We can do this since G^N = 1 and
// thus any other valid point on the elliptic curve has the same order.
func moduloReduce(k []byte) []byte {
// Since the order of G is curve.N, we can use a much smaller number by
// doing modulo curve.N
if len(k) > curveParams.ByteSize {
tmpK := new(big.Int).SetBytes(k)
tmpK.Mod(tmpK, curveParams.N)
return tmpK.Bytes()
}
return k
}
// ScalarMult returns k*(Bx, By) where k is a big endian integer.
//
// This is part of the elliptic.Curve interface implementation.
func (curve *KoblitzCurve) ScalarMult(Bx, By *big.Int, k []byte) (
*big.Int,
*big.Int,
) {
// Convert the affine coordinates from big integers to Jacobian points,
// do the multiplication in Jacobian projective space, and convert the
// Jacobian point back to affine big.Ints.
var kModN ModNScalar
kModN.SetByteSlice(moduloReduce(k))
var point, result JacobianPoint
bigAffineToJacobian(Bx, By, &point)
ScalarMultNonConst(&kModN, &point, &result)
return jacobianToBigAffine(&result)
}
// ScalarBaseMult returns k*G where G is the base point of the group and k is a
// big endian integer.
//
// This is part of the elliptic.Curve interface implementation.
func (curve *KoblitzCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
// Perform the multiplication and convert the Jacobian point back to affine
// big.Ints.
var kModN ModNScalar
kModN.SetByteSlice(moduloReduce(k))
var result JacobianPoint
ScalarBaseMultNonConst(&kModN, &result)
return jacobianToBigAffine(&result)
}
// X returns the x coordinate of the public key.
func (p *PublicKey) X() *big.Int {
return new(big.Int).SetBytes(p.x.Bytes()[:])
}
// Y returns the y coordinate of the public key.
func (p *PublicKey) Y() *big.Int {
return new(big.Int).SetBytes(p.y.Bytes()[:])
}
// ToECDSA returns the public key as a *ecdsa.PublicKey.
func (p *PublicKey) ToECDSA() *ecdsa.PublicKey {
return &ecdsa.PublicKey{
Curve: S256(),
X: p.X(),
Y: p.Y(),
}
}
// ToECDSA returns the secret key as a *ecdsa.SecretKey.
func (p *SecretKey) ToECDSA() *ecdsa.PrivateKey {
var secretKeyBytes [SecKeyBytesLen]byte
p.Key.PutBytes(&secretKeyBytes)
var result JacobianPoint
ScalarBaseMultNonConst(&p.Key, &result)
x, y := jacobianToBigAffine(&result)
newSecKey := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: S256(),
X: x,
Y: y,
},
D: new(big.Int).SetBytes(secretKeyBytes[:]),
}
zeroArray32(&secretKeyBytes)
return newSecKey
}
// fromHex converts the passed hex string into a big integer pointer and will
// panic is there is an error. This is only provided for the hard-coded
// constants so errors in the source code can bet detected. It will only (and
// must only) be called for initialization purposes.
func fromHex(s string) *big.Int {
if s == "" {
return big.NewInt(0)
}
r, ok := new(big.Int).SetString(s, 16)
if !ok {
panic("invalid hex in source file: " + s)
}
return r
}
// secp256k1 is a global instance of the KoblitzCurve implementation which in
// turn embeds and implements elliptic.CurveParams.
var secp256k1 = &KoblitzCurve{
CurveParams: &elliptic.CurveParams{
P: curveParams.P,
N: curveParams.N,
B: fromHex("0000000000000000000000000000000000000000000000000000000000000007"),
Gx: curveParams.Gx,
Gy: curveParams.Gy,
BitSize: curveParams.BitSize,
Name: "secp256k1",
},
}
// S256 returns an elliptic.Curve which implements secp256k1.
func S256() *KoblitzCurve {
return secp256k1
}

View File

@@ -0,0 +1,51 @@
// Copyright 2013-2016 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"testing"
)
// BenchmarkScalarBaseMultAdaptor benchmarks multiplying a scalar by the base
// point of the curve via the method used to satisfy the elliptic.Curve
// interface.
func BenchmarkScalarBaseMultAdaptor(b *testing.B) {
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
curve := S256()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
curve.ScalarBaseMult(k.Bytes())
}
}
// BenchmarkScalarBaseMultLargeAdaptor benchmarks multiplying an abnormally
// large scalar by the base point of the curve via the method used to satisfy
// the elliptic.Curve interface.
func BenchmarkScalarBaseMultLargeAdaptor(b *testing.B) {
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c005751111111011111110")
curve := S256()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
curve.ScalarBaseMult(k.Bytes())
}
}
// BenchmarkScalarMultAdaptor benchmarks multiplying a scalar by an arbitrary
// point on the curve via the method used to satisfy the elliptic.Curve
// interface.
func BenchmarkScalarMultAdaptor(b *testing.B) {
x := fromHex("34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6")
y := fromHex("0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232")
k := fromHex("d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575")
curve := S256()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
curve.ScalarMult(x, y, k.Bytes())
}
}

View File

@@ -0,0 +1,427 @@
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"math/big"
"math/rand"
"testing"
"time"
"lol.mleku.dev/chk"
)
// randBytes returns a byte slice of the required size created from a random
// value generated by the passed rng.
func randBytes(t *testing.T, rng *rand.Rand, numBytes uint8) []byte {
t.Helper()
buf := make([]byte, numBytes)
if _, err := rng.Read(buf); chk.T(err) {
t.Fatalf("failed to read random: %v", err)
}
return buf
}
// TestIsOnCurveAdaptor ensures the IsOnCurve method used to satisfy the
// elliptic.Curve interface works as intended.
func TestIsOnCurveAdaptor(t *testing.T) {
s256 := S256()
if !s256.IsOnCurve(s256.Params().Gx, s256.Params().Gy) {
t.Fatal("generator point does not claim to be on the curve")
}
}
// isValidAffinePoint returns true if the point (x,y) is on the secp256k1 curve
// or is the point at infinity.
func isValidAffinePoint(x, y *big.Int) bool {
if x.Sign() == 0 && y.Sign() == 0 {
return true
}
return S256().IsOnCurve(x, y)
}
// TestAddAffineAdaptor tests addition of points in affine coordinates via the
// method used to satisfy the elliptic.Curve interface works as intended for
// some edge cases and known good values.
func TestAddAffineAdaptor(t *testing.T) {
tests := []struct {
name string // test description
x1, y1 string // hex encoded coordinates of first point to add
x2, y2 string // hex encoded coordinates of second point to add
x3, y3 string // hex encoded coordinates of expected point
}{
{
// Addition with the point at infinity (left hand side).
name: "∞ + P = P",
x1: "0",
y1: "0",
x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
x3: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
y3: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
}, {
// Addition with the point at infinity (right hand side).
name: "P + ∞ = P",
x1: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
y1: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
x2: "0",
y2: "0",
x3: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
y3: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
}, {
// Addition with different x values.
name: "P(x1, y1) + P(x2, y2)",
x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
x2: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
y2: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
x3: "fd5b88c21d3143518d522cd2796f3d726793c88b3e05636bc829448e053fed69",
y3: "21cf4f6a5be5ff6380234c50424a970b1f7e718f5eb58f68198c108d642a137f",
}, {
// Addition with same x opposite y.
name: "P(x, y) + P(x, -y) = ∞",
x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
y2: "f48e156428cf0276dc092da5856e182288d7569f97934a56fe44be60f0d359fd",
x3: "0",
y3: "0",
}, {
// Addition with same point.
name: "P(x, y) + P(x, y) = 2P",
x1: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
y1: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
x2: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
y2: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
x3: "59477d88ae64a104dbb8d31ec4ce2d91b2fe50fa628fb6a064e22582196b365b",
y3: "938dc8c0f13d1e75c987cb1a220501bd614b0d3dd9eb5c639847e1240216e3b6",
},
}
curve := S256()
for _, test := range tests {
// Parse the test data.
x1, y1 := fromHex(test.x1), fromHex(test.y1)
x2, y2 := fromHex(test.x2), fromHex(test.y2)
x3, y3 := fromHex(test.x3), fromHex(test.y3)
// Ensure the test data is using points that are actually on the curve
// (or the point at infinity).
if !isValidAffinePoint(x1, y1) {
t.Errorf("%s: first point is not on curve", test.name)
continue
}
if !isValidAffinePoint(x2, y2) {
t.Errorf("%s: second point is not on curve", test.name)
continue
}
if !isValidAffinePoint(x3, y3) {
t.Errorf("%s: expected point is not on curve", test.name)
continue
}
// Add the two points and ensure the result matches expected.
rx, ry := curve.Add(x1, y1, x2, y2)
if rx.Cmp(x3) != 0 || ry.Cmp(y3) != 0 {
t.Errorf(
"%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)",
test.name, rx, ry, x3, y3,
)
continue
}
}
}
// TestDoubleAffineAdaptor tests doubling of points in affine coordinates via
// the method used to satisfy the elliptic.Curve interface works as intended for
// some edge cases and known good values.
func TestDoubleAffineAdaptor(t *testing.T) {
tests := []struct {
name string // test description
x1, y1 string // hex encoded coordinates of point to double
x3, y3 string // hex encoded coordinates of expected point
}{
{
// Doubling the point at infinity is still the point at infinity.
name: "2*∞ = ∞ (point at infinity)",
x1: "0",
y1: "0",
x3: "0",
y3: "0",
}, {
name: "random point 1",
x1: "e41387ffd8baaeeb43c2faa44e141b19790e8ac1f7ff43d480dc132230536f86",
y1: "1b88191d430f559896149c86cbcb703193105e3cf3213c0c3556399836a2b899",
x3: "88da47a089d333371bd798c548ef7caae76e737c1980b452d367b3cfe3082c19",
y3: "3b6f659b09a362821dfcfefdbfbc2e59b935ba081b6c249eb147b3c2100b1bc1",
}, {
name: "random point 2",
x1: "b3589b5d984f03ef7c80aeae444f919374799edf18d375cab10489a3009cff0c",
y1: "c26cf343875b3630e15bccc61202815b5d8f1fd11308934a584a5babe69db36a",
x3: "e193860172998751e527bb12563855602a227fc1f612523394da53b746bb2fb1",
y3: "2bfcf13d2f5ab8bb5c611fab5ebbed3dc2f057062b39a335224c22f090c04789",
}, {
name: "random point 3",
x1: "2b31a40fbebe3440d43ac28dba23eee71c62762c3fe3dbd88b4ab82dc6a82340",
y1: "9ba7deb02f5c010e217607fd49d58db78ec273371ea828b49891ce2fd74959a1",
x3: "2c8d5ef0d343b1a1a48aa336078eadda8481cb048d9305dc4fdf7ee5f65973a2",
y3: "bb4914ac729e26d3cd8f8dc8f702f3f4bb7e0e9c5ae43335f6e94c2de6c3dc95",
}, {
name: "random point 4",
x1: "61c64b760b51981fab54716d5078ab7dffc93730b1d1823477e27c51f6904c7a",
y1: "ef6eb16ea1a36af69d7f66524c75a3a5e84c13be8fbc2e811e0563c5405e49bd",
x3: "5f0dcdd2595f5ad83318a0f9da481039e36f135005420393e72dfca985b482f4",
y3: "a01c849b0837065c1cb481b0932c441f49d1cab1b4b9f355c35173d93f110ae0",
},
}
curve := S256()
for _, test := range tests {
// Parse test data.
x1, y1 := fromHex(test.x1), fromHex(test.y1)
x3, y3 := fromHex(test.x3), fromHex(test.y3)
// Ensure the test data is using points that are actually on
// the curve (or the point at infinity).
if !isValidAffinePoint(x1, y1) {
t.Errorf("%s: first point is not on the curve", test.name)
continue
}
if !isValidAffinePoint(x3, y3) {
t.Errorf("%s: expected point is not on the curve", test.name)
continue
}
// Double the point and ensure the result matches expected.
rx, ry := curve.Double(x1, y1)
if rx.Cmp(x3) != 0 || ry.Cmp(y3) != 0 {
t.Errorf(
"%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)",
test.name, rx, ry, x3, y3,
)
continue
}
}
}
// TestScalarBaseMultAdaptor ensures the ScalarBaseMult method used to satisfy
// the elliptic.Curve interface works as intended for some edge cases and known
// good values.
func TestScalarBaseMultAdaptor(t *testing.T) {
tests := []struct {
name string // test description
k string // hex encoded scalar
rx, ry string // hex encoded coordinates of expected point
}{
{
name: "zero",
k: "0000000000000000000000000000000000000000000000000000000000000000",
rx: "0000000000000000000000000000000000000000000000000000000000000000",
ry: "0000000000000000000000000000000000000000000000000000000000000000",
}, {
name: "one (aka 1*G = G)",
k: "0000000000000000000000000000000000000000000000000000000000000001",
rx: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
ry: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
}, {
name: "group order - 1 (aka -1*G = -G)",
k: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
rx: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
ry: "b7c52588d95c3b9aa25b0403f1eef75702e84bb7597aabe663b82f6f04ef2777",
}, {
name: "known good point 1",
k: "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522",
rx: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
ry: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
}, {
name: "known good point 2",
k: "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3",
rx: "d74bf844b0862475103d96a611cf2d898447e288d34b360bc885cb8ce7c00575",
ry: "131c670d414c4546b88ac3ff664611b1c38ceb1c21d76369d7a7a0969d61d97d",
}, {
name: "known good point 3",
k: "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d",
rx: "e8aecc370aedd953483719a116711963ce201ac3eb21d3f3257bb48668c6a72f",
ry: "c25caf2f0eba1ddb2f0f3f47866299ef907867b7d27e95b3873bf98397b24ee1",
}, {
name: "known good point 4",
k: "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec",
rx: "14890e61fcd4b0bd92e5b36c81372ca6fed471ef3aa60a3e415ee4fe987daba1",
ry: "297b858d9f752ab42d3bca67ee0eb6dcd1c2b7b0dbe23397e66adc272263f982",
}, {
name: "known good point 5",
k: "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9",
rx: "f73c65ead01c5126f28f442d087689bfa08e12763e0cec1d35b01751fd735ed3",
ry: "f449a8376906482a84ed01479bd18882b919c140d638307f0c0934ba12590bde",
},
}
curve := S256()
for _, test := range tests {
// Parse the test data.
k := fromHex(test.k)
xWant, yWant := fromHex(test.rx), fromHex(test.ry)
// Ensure the test data is using points that are actually on the curve
// (or the point at infinity).
if !isValidAffinePoint(xWant, yWant) {
t.Errorf("%s: expected point is not on curve", test.name)
continue
}
rx, ry := curve.ScalarBaseMult(k.Bytes())
if rx.Cmp(xWant) != 0 || ry.Cmp(yWant) != 0 {
t.Errorf(
"%s: wrong result:\ngot (%x, %x)\nwant (%x, %x)",
test.name, rx, ry, xWant, yWant,
)
}
}
}
// TestScalarBaseMultAdaptorRandom ensures that the ScalarBaseMult method used
// to satisfy the elliptic.Curve interface works as intended for
// randomly-generated scalars of all lengths up to 40 bytes.
func TestScalarBaseMultAdaptorRandom(t *testing.T) {
// Use a unique random seed each test instance and log it if the tests fail.
seed := time.Now().Unix()
rng := rand.New(rand.NewSource(seed))
defer func(t *testing.T, seed int64) {
if t.Failed() {
t.Logf("random seed: %d", seed)
}
}(t, seed)
s256 := S256()
const maxBytes = 40
const iterations = 10
for numBytes := uint8(1); numBytes < maxBytes; numBytes++ {
for i := 0; i < iterations; i++ {
// Generate a random scalar of the current length.
k := randBytes(t, rng, numBytes)
// Ensure the correct results by performing the multiplication with
// both the func under test as well as the generic scalar mult func.
x, y := s256.ScalarBaseMult(k)
xWant, yWant := s256.ScalarMult(s256.Gx, s256.Gy, k)
if x.Cmp(xWant) != 0 || y.Cmp(yWant) != 0 {
t.Errorf(
"bad output for %x: got (%x, %x), want (%x, %x)", k,
x, y, xWant, yWant,
)
continue
}
}
}
}
// TestScalarMultAdaptor ensures the ScalarMult method used to satisfy the
// elliptic.Curve interface works as intended for some edge cases and known good
// values.
func TestScalarMultAdaptor(t *testing.T) {
tests := []struct {
name string // test description
k string // hex encoded scalar
x, y string // hex encoded coordinates of point to multiply
rx, ry string // hex encoded coordinates of expected point
}{
{
name: "0*P = ∞ (point at infinity)",
k: "0",
x: "7e660beda020e9cc20391cef85374576853b0f22b8925d5d81c5845bb834c21e",
y: "2d114a5edb320cc9806527d1daf1bbb96a8fedc6f9e8ead421eaef2c7208e409",
rx: "0",
ry: "0",
}, {
name: "1*P = P",
k: "1",
x: "c00be8830995d1e44f1420dd3b90d3441fb66f6861c84a35f959c495a3be5440",
y: "ecf9665e6eba45720de652a340600c7356efe24d228bfe6ea2043e7791c51bb7",
rx: "c00be8830995d1e44f1420dd3b90d3441fb66f6861c84a35f959c495a3be5440",
ry: "ecf9665e6eba45720de652a340600c7356efe24d228bfe6ea2043e7791c51bb7",
}, {
name: "(group order - 1)*P = -P (aka -1*P = -P)",
k: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
x: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
y: "cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08",
rx: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
ry: "339ea810e73639c329e6c27c9ce4415ff6c1f6976bd173cc2a8c80276f1f2127",
}, {
name: "(group order - 1)*-P = P (aka -1*-P = -P, with P from prev test)",
k: "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
x: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
y: "339ea810e73639c329e6c27c9ce4415ff6c1f6976bd173cc2a8c80276f1f2127",
rx: "74a1ad6b5f76e39db2dd249410eac7f99e74c59cb83d2d0ed5ff1543da7703e9",
ry: "cc6157ef18c9c63cd6193d83631bbea0093e0968942e8c33d5737fd790e0db08",
}, {
name: "known good point from base mult tests (aka k*G)",
k: "aa5e28d6a97a2479a65527f7290311a3624d4cc0fa1578598ee3c2613bf99522",
x: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
y: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
rx: "34f9460f0e4f08393d192b3c5133a6ba099aa0ad9fd54ebccfacdfa239ff49c6",
ry: "0b71ea9bd730fd8923f6d25a7a91e7dd7728a960686cb5a901bb419e0f2ca232",
}, {
name: "known good result 1",
k: "7e2b897b8cebc6361663ad410835639826d590f393d90a9538881735256dfae3",
x: "1697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5",
y: "b9c398f186806f5d27561506e4557433a2cf15009e498ae7adee9d63d01b2396",
rx: "6951f3b50aafbc63e21707dd53623b7f42badd633a0567ef1b37f6e42a4237ad",
ry: "9c930796a49110122fbfdedc36418af726197ed950b783a2d29058f8c02130de",
}, {
name: "known good result 2",
k: "6461e6df0fe7dfd05329f41bf771b86578143d4dd1f7866fb4ca7e97c5fa945d",
x: "659214ac1a1790023f53c4cf55a0a63b9e20c1151efa971215b395a558aa151",
y: "b126363aa4243d2759320a356230569a4eea355d9dabd94ed7f4590701e5364d",
rx: "4ffad856833396ef753c0bd4ea40319295f107c476793df0adac2caea53b3df4",
ry: "586fa6b1e9a3ff7df8a2b9b3698badcf40aa06af5600fefc56dd8ae4db5451c5",
}, {
name: "known good result 3",
k: "376a3a2cdcd12581efff13ee4ad44c4044b8a0524c42422a7e1e181e4deeccec",
x: "3f0e80e574456d8f8fa64e044b2eb72ea22eb53fe1efe3a443933aca7f8cb0e3",
y: "cb66d7d7296cbc91e90b9c08485d01b39501253aa65b53a4cb0289e2ea5f404f",
rx: "35ae6480b18e48070709d9276ed97a50c6ee1fc05ac44386c85826533233d28f",
ry: "f88abee3efabd95e80ce8c664bbc3d4d12b24e1a0f4d2b98ba6542789c6715fd",
}, {
name: "known good result 4",
k: "1b22644a7be026548810c378d0b2994eefa6d2b9881803cb02ceff865287d1b9",
x: "d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e",
y: "581e2872a86c72a683842ec228cc6defea40af2bd896d3a5c504dc9ff6a26b58",
rx: "cca7f9a4b0d379c31c438050e163a8945f2f910498bd3b545be20ed862bd6cd9",
ry: "cfc7bbf37bef62da6e5753ed419168fa1376a3fe949c139a8dd0f5303f4ae947",
}, {
name: "known good result 5",
k: "7f5b2cb4b43840c75e4afad83d792e1965d8c21c1109505f45c7d46df422d73e",
x: "bce74de6d5f98dc027740c2bbff05b6aafe5fd8d103f827e48894a2bd3460117",
y: "5bea1fa17a41b115525a3e7dbf0d8d5a4f7ce5c6fc73a6f4f216512417c9f6b4",
rx: "3d96b9290fe6c4f2d62fe2175f4333907d0c3637fada1010b45c7d80690e16de",
ry: "d59c0e8192d7fbd4846172d6479630b751cd03d0d9be0dca2759c6212b70575d",
}, {
// From btcd issue #709.
name: "early implementation regression point",
k: "a2e8ba2e8ba2e8ba2e8ba2e8ba2e8ba219b51835b55cc30ebfe2f6599bc56f58",
x: "000000000000000000000000000000000000000000000000000000000000002c",
y: "420e7a99bba18a9d3952597510fd2b6728cfeafc21a4e73951091d4d8ddbe94e",
rx: "a2112dcdfbcd10ae1133a358de7b82db68e0a3eb4b492cc8268d1e7118c98788",
ry: "27fc7463b7bb3c5f98ecf2c84a6272bb1681ed553d92c69f2dfe25a9f9fd3836",
},
}
curve := S256()
for _, test := range tests {
// Parse the test data.
k := fromHex(test.k)
x, y := fromHex(test.x), fromHex(test.y)
xWant, yWant := fromHex(test.rx), fromHex(test.ry)
// Ensure the test data is using points that are actually on the curve
// (or the point at infinity).
if !isValidAffinePoint(x, y) {
t.Errorf("%s: point is not on curve", test.name)
continue
}
if !isValidAffinePoint(xWant, yWant) {
t.Errorf("%s: expected point is not on curve", test.name)
continue
}
// Perform scalar point multiplication ensure the result matches
// expected.
rx, ry := curve.ScalarMult(x, y, k.Bytes())
if rx.Cmp(xWant) != 0 || ry.Cmp(yWant) != 0 {
t.Errorf(
"%s: wrong result\ngot: (%x, %x)\nwant: (%x, %x)",
test.name, rx, ry, xWant, yWant,
)
}
}
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
// ErrorKind identifies a kind of error. It has full support for errors.Is and
// errors.As, so the caller can directly check against an error kind when
// determining the reason for an error.
type ErrorKind string
// These constants are used to identify a specific RuleError.
const (
// ErrPubKeyInvalidLen indicates that the length of a serialized public
// key is not one of the allowed lengths.
ErrPubKeyInvalidLen = ErrorKind("ErrPubKeyInvalidLen")
// ErrPubKeyInvalidFormat indicates an attempt was made to parse a public
// key that does not specify one of the supported formats.
ErrPubKeyInvalidFormat = ErrorKind("ErrPubKeyInvalidFormat")
// ErrPubKeyXTooBig indicates that the x coordinate for a public key
// is greater than or equal to the prime of the field underlying the group.
ErrPubKeyXTooBig = ErrorKind("ErrPubKeyXTooBig")
// ErrPubKeyYTooBig indicates that the y coordinate for a public key is
// greater than or equal to the prime of the field underlying the group.
ErrPubKeyYTooBig = ErrorKind("ErrPubKeyYTooBig")
// ErrPubKeyNotOnCurve indicates that a public key is not a point on the
// secp256k1 curve.
ErrPubKeyNotOnCurve = ErrorKind("ErrPubKeyNotOnCurve")
// ErrPubKeyMismatchedOddness indicates that a hybrid public key specified
// an oddness of the y coordinate that does not match the actual oddness of
// the provided y coordinate.
ErrPubKeyMismatchedOddness = ErrorKind("ErrPubKeyMismatchedOddness")
)
// Error satisfies the error interface and prints human-readable errors.
func (err ErrorKind) Error() string { return string(err) }
// Error identifies an error related to public key cryptography using a
// sec256k1 curve. It has full support for errors.Is and errors.As, so the
// caller can ascertain the specific reason for the error by checking
// the underlying error.
type Error struct {
Err error
Description string
}
// Error satisfies the error interface and prints human-readable errors.
func (err Error) Error() string { return err.Description }
// Unwrap returns the underlying wrapped error.
func (err Error) Unwrap() (ee error) { return err.Err }
// makeError creates an Error given a set of arguments.
func makeError(kind ErrorKind, desc string) (err error) {
return Error{Err: kind, Description: desc}
}

View File

@@ -0,0 +1,136 @@
// Copyright (c) 2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"errors"
"testing"
)
// TestErrorKindStringer tests the stringized output for the ErrorKind type.
func TestErrorKindStringer(t *testing.T) {
tests := []struct {
in ErrorKind
want string
}{
{ErrPubKeyInvalidLen, "ErrPubKeyInvalidLen"},
{ErrPubKeyInvalidFormat, "ErrPubKeyInvalidFormat"},
{ErrPubKeyXTooBig, "ErrPubKeyXTooBig"},
{ErrPubKeyYTooBig, "ErrPubKeyYTooBig"},
{ErrPubKeyNotOnCurve, "ErrPubKeyNotOnCurve"},
{ErrPubKeyMismatchedOddness, "ErrPubKeyMismatchedOddness"},
}
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
continue
}
}
}
// TestError tests the error output for the Error type.
func TestError(t *testing.T) {
tests := []struct {
in Error
want string
}{
{
Error{Description: "some error"},
"some error",
}, {
Error{Description: "human-readable error"},
"human-readable error",
},
}
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
continue
}
}
}
// TestErrorKindIsAs ensures both ErrorKind and Error can be identified as being
// a specific error kind via errors.Is and unwrapped via errors.As.
func TestErrorKindIsAs(t *testing.T) {
tests := []struct {
name string
err error
target error
wantMatch bool
wantAs ErrorKind
}{
{
name: "ErrPubKeyInvalidLen == ErrPubKeyInvalidLen",
err: ErrPubKeyInvalidLen,
target: ErrPubKeyInvalidLen,
wantMatch: true,
wantAs: ErrPubKeyInvalidLen,
}, {
name: "Error.ErrPubKeyInvalidLen == ErrPubKeyInvalidLen",
err: makeError(ErrPubKeyInvalidLen, ""),
target: ErrPubKeyInvalidLen,
wantMatch: true,
wantAs: ErrPubKeyInvalidLen,
}, {
name: "Error.ErrPubKeyInvalidLen == Error.ErrPubKeyInvalidLen",
err: makeError(ErrPubKeyInvalidLen, ""),
target: makeError(ErrPubKeyInvalidLen, ""),
wantMatch: true,
wantAs: ErrPubKeyInvalidLen,
}, {
name: "ErrPubKeyInvalidFormat != ErrPubKeyInvalidLen",
err: ErrPubKeyInvalidFormat,
target: ErrPubKeyInvalidLen,
wantMatch: false,
wantAs: ErrPubKeyInvalidFormat,
}, {
name: "Error.ErrPubKeyInvalidFormat != ErrPubKeyInvalidLen",
err: makeError(ErrPubKeyInvalidFormat, ""),
target: ErrPubKeyInvalidLen,
wantMatch: false,
wantAs: ErrPubKeyInvalidFormat,
}, {
name: "ErrPubKeyInvalidFormat != Error.ErrPubKeyInvalidLen",
err: ErrPubKeyInvalidFormat,
target: makeError(ErrPubKeyInvalidLen, ""),
wantMatch: false,
wantAs: ErrPubKeyInvalidFormat,
}, {
name: "Error.ErrPubKeyInvalidFormat != Error.ErrPubKeyInvalidLen",
err: makeError(ErrPubKeyInvalidFormat, ""),
target: makeError(ErrPubKeyInvalidLen, ""),
wantMatch: false,
wantAs: ErrPubKeyInvalidFormat,
},
}
for _, test := range tests {
// Ensure the error matches or not depending on the expected result.
result := errors.Is(test.err, test.target)
if result != test.wantMatch {
t.Errorf(
"%s: incorrect error identification -- got %v, want %v",
test.name, result, test.wantMatch,
)
continue
}
// Ensure the underlying error code can be unwrapped and is the expected
// code.
var kind ErrorKind
if !errors.As(test.err, &kind) {
t.Errorf("%s: unable to unwrap to error code", test.name)
continue
}
if kind != test.wantAs {
t.Errorf(
"%s: unexpected unwrapped error code -- got %v, want %v",
test.name, kind, test.wantAs,
)
continue
}
}
}

View File

@@ -0,0 +1,132 @@
// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1_test
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"fmt"
"next.orly.dev/pkg/crypto/ec/secp256k1"
"next.orly.dev/pkg/crypto/sha256"
"next.orly.dev/pkg/encoders/hex"
)
// This example demonstrates use of GenerateSharedSecret to encrypt a message
// for a recipient's public key, and subsequently decrypt the message using the
// recipient's secret key.
func Example_encryptDecryptMessage() {
newAEAD := func(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(block)
}
// Decode the hex-encoded pubkey of the recipient.
pubKeyBytes, err := hex.Dec(
"04115c42e757b2efb7671c578530ec191a1359381e6a71127a9d37c486fd30da" +
"e57e76dc58f693bd7e7010358ce6b165e483a2921010db67ac11b1b51b651953d2",
) // uncompressed pubkey
if err != nil {
fmt.Println(err)
return
}
pubKey, err := secp256k1.ParsePubKey(pubKeyBytes)
if err != nil {
fmt.Println(err)
return
}
// Derive an ephemeral public/secret keypair for performing ECDHE with
// the recipient.
ephemeralSecKey, err := secp256k1.GenerateSecretKey()
if err != nil {
fmt.Println(err)
return
}
ephemeralPubKey := ephemeralSecKey.PubKey().SerializeCompressed()
// Using ECDHE, derive a shared symmetric key for encryption of the plaintext.
cipherKey := sha256.Sum256(
secp256k1.GenerateSharedSecret(
ephemeralSecKey,
pubKey,
),
)
// Seal the message using an AEAD. Here we use AES-256-GCM.
// The ephemeral public key must be included in this message, and becomes
// the authenticated data for the AEAD.
//
// Note that unless a unique nonce can be guaranteed, the ephemeral
// and/or shared keys must not be reused to encrypt different messages.
// Doing so destroys the security of the scheme. Random nonces may be
// used if XChaCha20-Poly1305 is used instead, but the message must then
// also encode the nonce (which we don't do here).
//
// Since a new ephemeral key is generated for every message ensuring there
// is no key reuse and AES-GCM permits the nonce to be used as a counter,
// the nonce is intentionally initialized to all zeros so it acts like the
// first (and only) use of a counter.
plaintext := []byte("test message")
aead, err := newAEAD(cipherKey[:])
if err != nil {
fmt.Println(err)
return
}
nonce := make([]byte, aead.NonceSize())
ciphertext := make([]byte, 4+len(ephemeralPubKey))
binary.LittleEndian.PutUint32(ciphertext, uint32(len(ephemeralPubKey)))
copy(ciphertext[4:], ephemeralPubKey)
ciphertext = aead.Seal(ciphertext, nonce, plaintext, ephemeralPubKey)
// The remainder of this example is performed by the recipient on the
// ciphertext shared by the sender.
//
// Decode the hex-encoded secret key.
pkBytes, err := hex.Dec(
"a11b0a4e1a132305652ee7a8eb7848f6ad5ea381e3ce20a2c086a2e388230811",
)
if err != nil {
fmt.Println(err)
return
}
secKey := secp256k1.SecKeyFromBytes(pkBytes)
// Read the sender's ephemeral public key from the start of the message.
// Error handling for inappropriate pubkey lengths is elided here for
// brevity.
pubKeyLen := binary.LittleEndian.Uint32(ciphertext[:4])
senderPubKeyBytes := ciphertext[4 : 4+pubKeyLen]
senderPubKey, err := secp256k1.ParsePubKey(senderPubKeyBytes)
if err != nil {
fmt.Println(err)
return
}
// Derive the key used to seal the message, this time from the
// recipient's secret key and the sender's public key.
recoveredCipherKey := sha256.Sum256(
secp256k1.GenerateSharedSecret(
secKey,
senderPubKey,
),
)
// Open the sealed message.
aead, err = newAEAD(recoveredCipherKey[:])
if err != nil {
fmt.Println(err)
return
}
nonce = make([]byte, aead.NonceSize())
recoveredPlaintext, err := aead.Open(
nil, nonce, ciphertext[4+pubKeyLen:],
senderPubKeyBytes,
)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(recoveredPlaintext))
// Output:
// test message
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
// Copyright (c) 2020-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"math/big"
"testing"
)
// BenchmarkFieldNormalize benchmarks how long it takes the internal field
// to perform normalization (which includes modular reduction).
func BenchmarkFieldNormalize(b *testing.B) {
// The function is constant time so any value is fine.
f := &FieldVal{
n: [10]uint32{
0x000148f6, 0x03ffffc0, 0x03ffffff, 0x03ffffff, 0x03ffffff,
0x03ffffff, 0x03ffffff, 0x03ffffff, 0x03ffffff, 0x00000007,
},
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Normalize()
}
}
// BenchmarkFieldSqrt benchmarks calculating the square root of an unsigned
// 256-bit big-endian integer modulo the field prime with the specialized type.
func BenchmarkFieldSqrt(b *testing.B) {
// The function is constant time so any value is fine.
valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca"
f := new(FieldVal).SetHex(valHex).Normalize()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result FieldVal
_ = result.SquareRootVal(f)
}
}
// BenchmarkBigSqrt benchmarks calculating the square root of an unsigned
// 256-bit big-endian integer modulo the field prime with stdlib big integers.
func BenchmarkBigSqrt(b *testing.B) {
// The function is constant time so any value is fine.
valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca"
val, ok := new(big.Int).SetString(valHex, 16)
if !ok {
b.Fatalf("failed to parse hex %s", valHex)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = new(big.Int).ModSqrt(val, curveParams.P)
}
}
// BenchmarkFieldIsGtOrEqPrimeMinusOrder benchmarks determining whether a value
// is greater than or equal to the field prime minus the group order with the
// specialized type.
func BenchmarkFieldIsGtOrEqPrimeMinusOrder(b *testing.B) {
// The function is constant time so any value is fine.
valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca"
f := new(FieldVal).SetHex(valHex).Normalize()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = f.IsGtOrEqPrimeMinusOrder()
}
}
// BenchmarkBigIsGtOrEqPrimeMinusOrder benchmarks determining whether a value
// is greater than or equal to the field prime minus the group order with stdlib
// big integers.
func BenchmarkBigIsGtOrEqPrimeMinusOrder(b *testing.B) {
// Same value used in field val version.
valHex := "16fb970147a9acc73654d4be233cc48b875ce20a2122d24f073d29bd28805aca"
val, ok := new(big.Int).SetString(valHex, 16)
if !ok {
b.Fatalf("failed to parse hex %s", valHex)
}
bigPMinusN := new(big.Int).Sub(curveParams.P, curveParams.N)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// In practice, the internal value to compare would have to be converted
// to a big integer from bytes, so it's a fair comparison to allocate a
// new big int here and set all bytes.
_ = new(big.Int).SetBytes(val.Bytes()).Cmp(bigPMinusN) >= 0
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
// Copyright 2015 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"compress/zlib"
"encoding/base64"
"io"
"strings"
"sync"
)
//go:generate go run genprecomps.go
// bytePointTable describes a table used to house pre-computed values for
// accelerating scalar base multiplication.
type bytePointTable [32][256]JacobianPoint
// compressedBytePointsFn is set to a real function by the code generation to
// return the compressed pre-computed values for accelerating scalar base
// multiplication.
var compressedBytePointsFn func() string
// s256BytePoints houses pre-computed values used to accelerate scalar base
// multiplication such that they are only loaded on first use.
var s256BytePoints = func() func() *bytePointTable {
// mustLoadBytePoints decompresses and deserializes the pre-computed byte
// points used to accelerate scalar base multiplication for the secp256k1
// curve.
//
// This approach is used since it allows the compile to use significantly
// less ram and be performed much faster than it is with hard-coding the
// final in-memory data structure. At the same time, it is quite fast to
// generate the in-memory data structure on first use with this approach
// versus computing the table.
//
// It will panic on any errors because the data is hard coded and thus any
// errors means something is wrong in the source code.
var data *bytePointTable
mustLoadBytePoints := func() {
// There will be no byte points to load when generating them.
if compressedBytePointsFn == nil {
return
}
bp := compressedBytePointsFn()
// Decompress the pre-computed table used to accelerate scalar base
// multiplication.
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bp))
r, err := zlib.NewReader(decoder)
if err != nil {
panic(err)
}
serialized, err := io.ReadAll(r)
if err != nil {
panic(err)
}
// Deserialize the precomputed byte points and set the memory table to
// them.
offset := 0
var bytePoints bytePointTable
for byteNum := 0; byteNum < len(bytePoints); byteNum++ {
// All points in this window.
for i := 0; i < len(bytePoints[byteNum]); i++ {
p := &bytePoints[byteNum][i]
p.X.SetByteSlice(serialized[offset:])
offset += 32
p.Y.SetByteSlice(serialized[offset:])
offset += 32
p.Z.SetInt(1)
}
}
data = &bytePoints
}
// Return a closure that initializes the data on first access. This is done
// because the table takes a non-trivial amount of memory and initializing
// it unconditionally would cause anything that imports the package, either
// directly, or indirectly via transitive deps, to use that memory even if
// the caller never accesses any parts of the package that actually needs
// access to it.
var loadBytePointsOnce sync.Once
return func() *bytePointTable {
loadBytePointsOnce.Do(mustLoadBytePoints)
return data
}
}()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,274 @@
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"math/big"
"testing"
)
// benchmarkVals returns the raw bytes for a couple of unsigned 256-bit
// big-endian integers used throughout the benchmarks.
func benchmarkVals() [2][]byte {
return [2][]byte{
hexToBytes("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364143"),
hexToBytes("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364144"),
}
}
// BenchmarkBigIntModN benchmarks setting and reducing an unsigned 256-bit
// big-endian integer modulo the group order with stdlib big integers.
func BenchmarkBigIntModN(b *testing.B) {
buf := benchmarkVals()[0]
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := new(big.Int).SetBytes(buf)
v.Mod(v, curveParams.N)
}
}
// BenchmarkModNScalar benchmarks setting and reducing an unsigned 256-bit
// big-endian integer modulo the group order with the specialized type.
func BenchmarkModNScalar(b *testing.B) {
slice := benchmarkVals()[0]
var buf [32]byte
copy(buf[:], slice)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var s ModNScalar
s.SetBytes(&buf)
}
}
// BenchmarkBigIntZero benchmarks zeroing an unsigned 256-bit big-endian
// integer modulo the group order with stdlib big integers.
func BenchmarkBigIntZero(b *testing.B) {
v1 := new(big.Int).SetBytes(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v1.SetUint64(0)
}
}
// BenchmarkModNScalarZero benchmarks zeroing an unsigned 256-bit big-endian
// integer modulo the group order with the specialized type.
func BenchmarkModNScalarZero(b *testing.B) {
var s1 ModNScalar
s1.SetByteSlice(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s1.Zero()
}
}
// BenchmarkBigIntIsZero benchmarks determining if an unsigned 256-bit
// big-endian integer modulo the group order is zero with stdlib big integers.
func BenchmarkBigIntIsZero(b *testing.B) {
v1 := new(big.Int).SetBytes(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = v1.Sign() == 0
}
}
// BenchmarkModNScalarIsZero benchmarks determining if an unsigned 256-bit
// big-endian integer modulo the group order is zero with the specialized type.
func BenchmarkModNScalarIsZero(b *testing.B) {
var s1 ModNScalar
s1.SetByteSlice(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = s1.IsZero()
}
}
// BenchmarkBigIntEquals benchmarks determining equality between two unsigned
// 256-bit big-endian integers modulo the group order with stdlib big integers.
func BenchmarkBigIntEquals(b *testing.B) {
bufs := benchmarkVals()
v1 := new(big.Int).SetBytes(bufs[0])
v2 := new(big.Int).SetBytes(bufs[1])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
v1.Cmp(v2)
}
}
// BenchmarkModNScalarEquals benchmarks determining equality between two
// unsigned 256-bit big-endian integers modulo the group order with the
// specialized type.
func BenchmarkModNScalarEquals(b *testing.B) {
bufs := benchmarkVals()
var s1, s2 ModNScalar
s1.SetByteSlice(bufs[0])
s2.SetByteSlice(bufs[1])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s1.Equals(&s2)
}
}
// BenchmarkBigIntAddModN benchmarks adding two unsigned 256-bit big-endian
// integers modulo the group order with stdlib big integers.
func BenchmarkBigIntAddModN(b *testing.B) {
bufs := benchmarkVals()
v1 := new(big.Int).SetBytes(bufs[0])
v2 := new(big.Int).SetBytes(bufs[1])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := new(big.Int).Add(v1, v2)
result.Mod(result, curveParams.N)
}
}
// BenchmarkModNScalarAdd benchmarks adding two unsigned 256-bit big-endian
// integers modulo the group order with the specialized type.
func BenchmarkModNScalarAdd(b *testing.B) {
bufs := benchmarkVals()
var s1, s2 ModNScalar
s1.SetByteSlice(bufs[0])
s2.SetByteSlice(bufs[1])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = new(ModNScalar).Add2(&s1, &s2)
}
}
// BenchmarkBigIntMulModN benchmarks multiplying two unsigned 256-bit big-endian
// integers modulo the group order with stdlib big integers.
func BenchmarkBigIntMulModN(b *testing.B) {
bufs := benchmarkVals()
v1 := new(big.Int).SetBytes(bufs[0])
v2 := new(big.Int).SetBytes(bufs[1])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := new(big.Int).Mul(v1, v2)
result.Mod(result, curveParams.N)
}
}
// BenchmarkModNScalarMul benchmarks multiplying two unsigned 256-bit big-endian
// integers modulo the group order with the specialized type.
func BenchmarkModNScalarMul(b *testing.B) {
bufs := benchmarkVals()
var s1, s2 ModNScalar
s1.SetByteSlice(bufs[0])
s2.SetByteSlice(bufs[1])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = new(ModNScalar).Mul2(&s1, &s2)
}
}
// BenchmarkBigIntSquareModN benchmarks squaring an unsigned 256-bit big-endian
// integer modulo the group order is zero with stdlib big integers.
func BenchmarkBigIntSquareModN(b *testing.B) {
v1 := new(big.Int).SetBytes(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := new(big.Int).Mul(v1, v1)
result.Mod(result, curveParams.N)
}
}
// BenchmarkModNScalarSquare benchmarks squaring an unsigned 256-bit big-endian
// integer modulo the group order is zero with the specialized type.
func BenchmarkModNScalarSquare(b *testing.B) {
var s1 ModNScalar
s1.SetByteSlice(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = new(ModNScalar).SquareVal(&s1)
}
}
// BenchmarkBigIntNegateModN benchmarks negating an unsigned 256-bit big-endian
// integer modulo the group order is zero with stdlib big integers.
func BenchmarkBigIntNegateModN(b *testing.B) {
v1 := new(big.Int).SetBytes(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
result := new(big.Int).Neg(v1)
result.Mod(result, curveParams.N)
}
}
// BenchmarkModNScalarNegate benchmarks negating an unsigned 256-bit big-endian
// integer modulo the group order is zero with the specialized type.
func BenchmarkModNScalarNegate(b *testing.B) {
var s1 ModNScalar
s1.SetByteSlice(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = new(ModNScalar).NegateVal(&s1)
}
}
// BenchmarkBigIntInverseModN benchmarks calculating the multiplicative inverse
// of an unsigned 256-bit big-endian integer modulo the group order is zero with
// stdlib big integers.
func BenchmarkBigIntInverseModN(b *testing.B) {
v1 := new(big.Int).SetBytes(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
new(big.Int).ModInverse(v1, curveParams.N)
}
}
// BenchmarkModNScalarInverse benchmarks calculating the multiplicative inverse
// of an unsigned 256-bit big-endian integer modulo the group order is zero with
// the specialized type.
func BenchmarkModNScalarInverse(b *testing.B) {
var s1 ModNScalar
s1.SetByteSlice(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = new(ModNScalar).InverseValNonConst(&s1)
}
}
// BenchmarkBigIntIsOverHalfOrder benchmarks determining if an unsigned 256-bit
// big-endian integer modulo the group order exceeds half the group order with
// stdlib big integers.
func BenchmarkBigIntIsOverHalfOrder(b *testing.B) {
v1 := new(big.Int).SetBytes(benchmarkVals()[0])
bigHalfOrder := new(big.Int).Rsh(curveParams.N, 1)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = v1.Cmp(bigHalfOrder)
}
}
// BenchmarkModNScalarIsOverHalfOrder benchmarks determining if an unsigned
// 256-bit big-endian integer modulo the group order exceeds half the group
// order with the specialized type.
func BenchmarkModNScalarIsOverHalfOrder(b *testing.B) {
var s1 ModNScalar
s1.SetByteSlice(benchmarkVals()[0])
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
s1.IsOverHalfOrder()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"bytes"
"hash"
"next.orly.dev/pkg/crypto/sha256"
)
// References:
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
//
// [ISO/IEC 8825-1]: Information technology — ASN.1 encoding rules:
// Specification of Basic Encoding Rules (BER), Canonical Encoding Rules
// (CER) and Distinguished Encoding Rules (DER)
//
// [SEC1]: Elliptic Curve Cryptography (May 31, 2009, Version 2.0)
// https://www.secg.org/sec1-v2.pdf
var (
// singleZero is used during RFC6979 nonce generation. It is provided
// here to avoid the need to create it multiple times.
singleZero = []byte{0x00}
// zeroInitializer is used during RFC6979 nonce generation. It is provided
// here to avoid the need to create it multiple times.
zeroInitializer = bytes.Repeat([]byte{0x00}, sha256.BlockSize)
// singleOne is used during RFC6979 nonce generation. It is provided
// here to avoid the need to create it multiple times.
singleOne = []byte{0x01}
// oneInitializer is used during RFC6979 nonce generation. It is provided
// here to avoid the need to create it multiple times.
oneInitializer = bytes.Repeat([]byte{0x01}, sha256.Size)
)
// hmacsha256 implements a resettable version of HMAC-SHA256.
type hmacsha256 struct {
inner, outer hash.Hash
ipad, opad [sha256.BlockSize]byte
}
// Write adds data to the running hash.
func (h *hmacsha256) Write(p []byte) { h.inner.Write(p) }
// initKey initializes the HMAC-SHA256 instance to the provided key.
func (h *hmacsha256) initKey(key []byte) {
// Hash the key if it is too large.
if len(key) > sha256.BlockSize {
h.outer.Write(key)
key = h.outer.Sum(nil)
}
copy(h.ipad[:], key)
copy(h.opad[:], key)
for i := range h.ipad {
h.ipad[i] ^= 0x36
}
for i := range h.opad {
h.opad[i] ^= 0x5c
}
h.inner.Write(h.ipad[:])
}
// ResetKey resets the HMAC-SHA256 to its initial state and then initializes it
// with the provided key. It is equivalent to creating a new instance with the
// provided key without allocating more memory.
func (h *hmacsha256) ResetKey(key []byte) {
h.inner.Reset()
h.outer.Reset()
copy(h.ipad[:], zeroInitializer)
copy(h.opad[:], zeroInitializer)
h.initKey(key)
}
// Resets the HMAC-SHA256 to its initial state using the current key.
func (h *hmacsha256) Reset() {
h.inner.Reset()
h.inner.Write(h.ipad[:])
}
// Sum returns the hash of the written data.
func (h *hmacsha256) Sum() []byte {
h.outer.Reset()
h.outer.Write(h.opad[:])
h.outer.Write(h.inner.Sum(nil))
return h.outer.Sum(nil)
}
// newHMACSHA256 returns a new HMAC-SHA256 hasher using the provided key.
func newHMACSHA256(key []byte) *hmacsha256 {
h := new(hmacsha256)
h.inner = sha256.New()
h.outer = sha256.New()
h.initKey(key)
return h
}
// NonceRFC6979 generates a nonce deterministically according to RFC 6979 using
// HMAC-SHA256 for the hashing function. It takes a 32-byte hash as an input
// and returns a 32-byte nonce to be used for deterministic signing. The extra
// and version arguments are optional, but allow additional data to be added to
// the input of the HMAC. When provided, the extra data must be 32-bytes and
// version must be 16 bytes or they will be ignored.
//
// Finally, the extraIterations parameter provides a method to produce a stream
// of deterministic nonces to ensure the signing code is able to produce a nonce
// that results in a valid signature in the extremely unlikely event the
// original nonce produced results in an invalid signature (e.g. R == 0).
// Signing code should start with 0 and increment it if necessary.
func NonceRFC6979(
secKey []byte, hash []byte, extra []byte, version []byte,
extraIterations uint32,
) *ModNScalar {
// Input to HMAC is the 32-byte secret key and the 32-byte hash. In
// addition, it may include the optional 32-byte extra data and 16-byte
// version. Create a fixed-size array to avoid extra allocs and slice it
// properly.
const (
secKeyLen = 32
hashLen = 32
extraLen = 32
versionLen = 16
)
var keyBuf [secKeyLen + hashLen + extraLen + versionLen]byte
// Truncate rightmost bytes of secret key and hash if they are too long and
// leave left padding of zeros when they're too short.
if len(secKey) > secKeyLen {
secKey = secKey[:secKeyLen]
}
if len(hash) > hashLen {
hash = hash[:hashLen]
}
offset := secKeyLen - len(secKey) // Zero left padding if needed.
offset += copy(keyBuf[offset:], secKey)
offset += hashLen - len(hash) // Zero left padding if needed.
offset += copy(keyBuf[offset:], hash)
if len(extra) == extraLen {
offset += copy(keyBuf[offset:], extra)
if len(version) == versionLen {
offset += copy(keyBuf[offset:], version)
}
} else if len(version) == versionLen {
// When the version was specified, but not the extra data, leave the
// extra data portion all zero.
offset += secKeyLen
offset += copy(keyBuf[offset:], version)
}
key := keyBuf[:offset]
// Step B.
//
// V = 0x01 0x01 0x01 ... 0x01 such that the length of V, in bits, is
// equal to 8*ceil(hashLen/8).
//
// Note that since the hash length is a multiple of 8 for the chosen hash
// function in this optimized implementation, the result is just the hash
// length, so avoid the extra calculations. Also, since it isn't modified,
// start with a global value.
v := oneInitializer
// Step C (Go zeroes all allocated memory).
//
// K = 0x00 0x00 0x00 ... 0x00 such that the length of K, in bits, is
// equal to 8*ceil(hashLen/8).
//
// As above, since the hash length is a multiple of 8 for the chosen hash
// function in this optimized implementation, the result is just the hash
// length, so avoid the extra calculations.
k := zeroInitializer[:hashLen]
// Step D.
//
// K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1))
//
// Note that key is the "int2octets(x) || bits2octets(h1)" portion along
// with potential additional data as described by section 3.6 of the RFC.
hasher := newHMACSHA256(k)
hasher.Write(oneInitializer)
hasher.Write(singleZero[:])
hasher.Write(key)
k = hasher.Sum()
// Step E.
//
// V = HMAC_K(V)
hasher.ResetKey(k)
hasher.Write(v)
v = hasher.Sum()
// Step ToSliceOfBytes.
//
// K = HMAC_K(V || 0x01 || int2octets(x) || bits2octets(h1))
//
// Note that key is the "int2octets(x) || bits2octets(h1)" portion along
// with potential additional data as described by section 3.6 of the RFC.
hasher.Reset()
hasher.Write(v)
hasher.Write(singleOne[:])
hasher.Write(key[:])
k = hasher.Sum()
// Step G.
//
// V = HMAC_K(V)
hasher.ResetKey(k)
hasher.Write(v)
v = hasher.Sum()
// Step H.
//
// Repeat until the value is nonzero and less than the curve order.
var generated uint32
for {
// Step H1 and H2.
//
// Set T to the empty sequence. The length of T (in bits) is denoted
// tlen; thus, at that point, tlen = 0.
//
// While tlen < qlen, do the following:
// V = HMAC_K(V)
// T = T || V
//
// Note that because the hash function output is the same length as the
// secret key in this optimized implementation, there is no need to
// loop or create an intermediate T.
hasher.Reset()
hasher.Write(v)
v = hasher.Sum()
// Step H3.
//
// k = bits2int(T)
// If k is within the range [1,q-1], return it.
//
// Otherwise, compute:
// K = HMAC_K(V || 0x00)
// V = HMAC_K(V)
var secret ModNScalar
overflow := secret.SetByteSlice(v)
if !overflow && !secret.IsZero() {
generated++
if generated > extraIterations {
return &secret
}
}
// K = HMAC_K(V || 0x00)
hasher.Reset()
hasher.Write(v)
hasher.Write(singleZero[:])
k = hasher.Sum()
// V = HMAC_K(V)
hasher.ResetKey(k)
hasher.Write(v)
v = hasher.Sum()
}
}

View File

@@ -0,0 +1,225 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"testing"
"next.orly.dev/pkg/crypto/sha256"
"next.orly.dev/pkg/encoders/hex"
"next.orly.dev/pkg/utils"
)
// hexToBytes converts the passed hex string into bytes 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 hexToBytes(s string) []byte {
b, err := hex.Dec(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
return b
}
// TestNonceRFC6979 ensures that the deterministic nonces generated by
// NonceRFC6979 produces the expected nonces, including things such as when
// providing extra data and version information, short hashes, and multiple
// iterations.
func TestNonceRFC6979(t *testing.T) {
tests := []struct {
name string
key string
hash string
extraData string
version string
iterations uint32
expected string
}{
{
name: "key 32 bytes, hash 32 bytes, no extra data, no version",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
// Should be same as key with 32 bytes due to zero padding.
name: "key <32 bytes, hash 32 bytes, no extra data, no version",
key: "11111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
// Should be same as key with 32 bytes due to truncation.
name: "key >32 bytes, hash 32 bytes, no extra data, no version",
key: "001111111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
name: "hash <32 bytes (padded), no extra data, no version",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "00000000000000000000000000000000000000000000000000000000000001",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
name: "hash >32 bytes (truncated), no extra data, no version",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "000000000000000000000000000000000000000000000000000000000000000100",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
name: "hash 32 bytes, extra data <32 bytes (ignored), no version",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
extraData: "00000000000000000000000000000000000000000000000000000000000002",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
name: "hash 32 bytes, extra data >32 bytes (ignored), no version",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
extraData: "000000000000000000000000000000000000000000000000000000000000000002",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
name: "hash 32 bytes, no extra data, version <16 bytes (ignored)",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
version: "000000000000000000000000000003",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
name: "hash 32 bytes, no extra data, version >16 bytes (ignored)",
key: "001111111111111111111111111111111111111111111111111111111111111122",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
version: "0000000000000000000000000000000003",
iterations: 0,
expected: "154e92760f77ad9af6b547edd6f14ad0fae023eb2221bc8be2911675d8a686a3",
}, {
name: "hash 32 bytes, extra data 32 bytes, no version",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
extraData: "0000000000000000000000000000000000000000000000000000000000000002",
iterations: 0,
expected: "67893461ade51cde61824b20bc293b585d058e6b9f40fb68453d5143f15116ae",
}, {
name: "hash 32 bytes, no extra data, version 16 bytes",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
version: "00000000000000000000000000000003",
iterations: 0,
expected: "7b27d6ceff87e1ded1860ca4e271a530e48514b9d3996db0af2bb8bda189007d",
}, {
// Should be same as no extra data + version specified due to padding.
name: "hash 32 bytes, extra data 32 bytes all zero, version 16 bytes",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
extraData: "0000000000000000000000000000000000000000000000000000000000000000",
version: "00000000000000000000000000000003",
iterations: 0,
expected: "7b27d6ceff87e1ded1860ca4e271a530e48514b9d3996db0af2bb8bda189007d",
}, {
name: "hash 32 bytes, extra data 32 bytes, version 16 bytes",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
extraData: "0000000000000000000000000000000000000000000000000000000000000002",
version: "00000000000000000000000000000003",
iterations: 0,
expected: "9b5657643dfd4b77d99dfa505ed8a17e1b9616354fc890669b4aabece2170686",
}, {
name: "hash 32 bytes, no extra data, no version, extra iteration",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
iterations: 1,
expected: "66fca3fe494a6216e4a3f15cfbc1d969c60d9cdefda1a1c193edabd34aa8cd5e",
}, {
name: "hash 32 bytes, no extra data, no version, 2 extra iterations",
key: "0011111111111111111111111111111111111111111111111111111111111111",
hash: "0000000000000000000000000000000000000000000000000000000000000001",
iterations: 2,
expected: "70da248c92b5d28a52eafca1848b1a37d4cb36526c02553c9c48bb0b895fc77d",
},
}
for _, test := range tests {
secKey := hexToBytes(test.key)
hash := hexToBytes(test.hash)
extraData := hexToBytes(test.extraData)
version := hexToBytes(test.version)
wantNonce := hexToBytes(test.expected)
// Ensure deterministically generated nonce is the expected value.
gotNonce := NonceRFC6979(
secKey, hash[:], extraData, version,
test.iterations,
)
gotNonceBytes := gotNonce.Bytes()
if !utils.FastEqual(gotNonceBytes[:], wantNonce) {
t.Errorf(
"%s: unexpected nonce -- got %x, want %x", test.name,
gotNonceBytes, wantNonce,
)
continue
}
}
}
// TestRFC6979Compat ensures that the deterministic nonces generated by
// NonceRFC6979 produces the expected nonces for known values from other
// implementations.
func TestRFC6979Compat(t *testing.T) {
// Test vectors matching Trezor and CoreBitcoin implementations.
// - https://github.com/trezor/trezor-crypto/blob/9fea8f8ab377dc514e40c6fd1f7c89a74c1d8dc6/tests.c#L432-L453
// - https://github.com/oleganza/CoreBitcoin/blob/e93dd71207861b5bf044415db5fa72405e7d8fbc/CoreBitcoin/BTCKey%2BTests.m#L23-L49
tests := []struct {
key string
msg string
nonce string
}{
{
"cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50",
"sample",
"2df40ca70e639d89528a6b670d9d48d9165fdc0febc0974056bdce192b8e16a3",
}, {
// This signature hits the case when S is higher than halforder.
// If S is not canonicalized (lowered by halforder), this test will fail.
"0000000000000000000000000000000000000000000000000000000000000001",
"Satoshi Nakamoto",
"8f8a276c19f4149656b280621e358cce24f5f52542772691ee69063b74f15d15",
}, {
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
"Satoshi Nakamoto",
"33a19b60e25fb6f4435af53a3d42d493644827367e6453928554f43e49aa6f90",
}, {
"f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181",
"Alan Turing",
"525a82b70e67874398067543fd84c83d30c175fdc45fdeee082fe13b1d7cfdf1",
}, {
"0000000000000000000000000000000000000000000000000000000000000001",
"All those moments will be lost in time, like tears in rain. Time to die...",
"38aa22d72376b4dbc472e06c3ba403ee0a394da63fc58d88686c611aba98d6b3",
}, {
"e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2",
"There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!",
"1f4b84c23a86a221d233f2521be018d9318639d5b8bbd6374a8a59232d16ad3d",
},
}
for i, test := range tests {
secKey := hexToBytes(test.key)
hash := sha256.Sum256([]byte(test.msg))
// Ensure deterministically generated nonce is the expected value.
gotNonce := NonceRFC6979(secKey, hash[:], nil, nil, 0)
wantNonce := hexToBytes(test.nonce)
gotNonceBytes := gotNonce.Bytes()
if !utils.FastEqual(gotNonceBytes[:], wantNonce) {
t.Errorf(
"NonceRFC6979 #%d (%s): Nonce is incorrect: "+
"%x (expected %x)", i, test.msg, gotNonce,
wantNonce,
)
continue
}
}
}

View File

@@ -0,0 +1,22 @@
package secp256k1
import (
_ "embed"
)
//go:embed rawbytepoints.bin
var bytepoints []byte
var BytePointTable [32][256]JacobianPoint
func init() {
var cursor int
for i := range BytePointTable {
for j := range BytePointTable[i] {
BytePointTable[i][j].X.SetByteSlice(bytepoints[cursor:])
cursor += 32
BytePointTable[i][j].Y.SetByteSlice(bytepoints[cursor:])
cursor += 32
BytePointTable[i][j].Z.SetInt(1)
}
}
}

View File

@@ -0,0 +1,3 @@
// Package main provides a generator for precomputed constants for secp256k1
// signatures. This has been updated to use go 1.16 embed library.
package main

View File

@@ -0,0 +1,340 @@
// Copyright 2015 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
// References:
// [GECC]: Guide to Elliptic Curve Cryptography (Hankerson, Menezes, Vanstone)
import (
"fmt"
"math/big"
"os"
"lol.mleku.dev/chk"
"lol.mleku.dev/log"
"next.orly.dev/pkg/crypto/ec/secp256k1"
)
// curveParams houses the secp256k1 curve parameters for convenient access.
var curveParams = secp256k1.Params()
// bigAffineToJacobian takes an affine point (x, y) as big integers and converts
// it to Jacobian point with Z=1.
func bigAffineToJacobian(x, y *big.Int, result *secp256k1.JacobianPoint) {
result.X.SetByteSlice(x.Bytes())
result.Y.SetByteSlice(y.Bytes())
result.Z.SetInt(1)
}
// serializedBytePoints returns a serialized byte slice which contains all possible points per
// 8-bit window. This is used to when generating compressedbytepoints.go.
func serializedBytePoints() []byte {
// Calculate G^(2^i) for i in 0..255. These are used to avoid recomputing
// them for each digit of the 8-bit windows.
doublingPoints := make([]secp256k1.JacobianPoint, curveParams.BitSize)
var q secp256k1.JacobianPoint
bigAffineToJacobian(curveParams.Gx, curveParams.Gy, &q)
for i := 0; i < curveParams.BitSize; i++ {
// Q = 2*Q.
doublingPoints[i] = q
secp256k1.DoubleNonConst(&q, &q)
}
// Separate the bits into byte-sized windows.
curveByteSize := curveParams.BitSize / 8
serialized := make([]byte, curveByteSize*256*2*32)
offset := 0
for byteNum := 0; byteNum < curveByteSize; byteNum++ {
// Grab the 8 bits that make up this byte from doubling points.
startingBit := 8 * (curveByteSize - byteNum - 1)
windowPoints := doublingPoints[startingBit : startingBit+8]
// Compute all points in this window, convert them to affine, and
// serialize them.
for i := 0; i < 256; i++ {
var point secp256k1.JacobianPoint
for bit := 0; bit < 8; bit++ {
if i>>uint(bit)&1 == 1 {
secp256k1.AddNonConst(&point, &windowPoints[bit], &point)
}
}
point.ToAffine()
point.X.PutBytesUnchecked(serialized[offset:])
offset += 32
point.Y.PutBytesUnchecked(serialized[offset:])
offset += 32
}
}
return serialized
}
// sqrt returns the square root of the provided big integer using Newton's
// method. It's only compiled and used during generation of pre-computed
// values, so speed is not a huge concern.
func sqrt(n *big.Int) *big.Int {
// Initial guess = 2^(log_2(n)/2)
guess := big.NewInt(2)
guess.Exp(guess, big.NewInt(int64(n.BitLen()/2)), nil)
// Now refine using Newton's method.
big2 := big.NewInt(2)
prevGuess := big.NewInt(0)
for {
prevGuess.Set(guess)
guess.Add(guess, new(big.Int).Div(n, guess))
guess.Div(guess, big2)
if guess.Cmp(prevGuess) == 0 {
break
}
}
return guess
}
// endomorphismParams houses the parameters needed to make use of the secp256k1
// endomorphism.
type endomorphismParams struct {
lambda *big.Int
beta *big.Int
a1, b1 *big.Int
a2, b2 *big.Int
z1, z2 *big.Int
}
// endomorphismVectors runs the first 3 steps of algorithm 3.74 from [GECC] to
// generate the linearly independent vectors needed to generate a balanced
// length-two representation of a multiplier such that k = k1 + k2λ (mod N) and
// returns them. Since the values will always be the same given the fact that N
// and λ are fixed, the final results can be accelerated by storing the
// precomputed values.
func endomorphismVectors(lambda *big.Int) (a1, b1, a2, b2 *big.Int) {
// This section uses an extended Euclidean algorithm to generate a
// sequence of equations:
// s[i] * N + t[i] * λ = r[i]
nSqrt := sqrt(curveParams.N)
u, v := new(big.Int).Set(curveParams.N), new(big.Int).Set(lambda)
x1, y1 := big.NewInt(1), big.NewInt(0)
x2, y2 := big.NewInt(0), big.NewInt(1)
q, r := new(big.Int), new(big.Int)
qu, qx1, qy1 := new(big.Int), new(big.Int), new(big.Int)
s, t := new(big.Int), new(big.Int)
ri, ti := new(big.Int), new(big.Int)
a1, b1, a2, b2 = new(big.Int), new(big.Int), new(big.Int), new(big.Int)
found, oneMore := false, false
for u.Sign() != 0 {
// q = v/u
q.Div(v, u)
// r = v - q*u
qu.Mul(q, u)
r.Sub(v, qu)
// s = x2 - q*x1
qx1.Mul(q, x1)
s.Sub(x2, qx1)
// t = y2 - q*y1
qy1.Mul(q, y1)
t.Sub(y2, qy1)
// v = u, u = r, x2 = x1, x1 = s, y2 = y1, y1 = t
v.Set(u)
u.Set(r)
x2.Set(x1)
x1.Set(s)
y2.Set(y1)
y1.Set(t)
// As soon as the remainder is less than the sqrt of n, the
// values of a1 and b1 are known.
if !found && r.Cmp(nSqrt) < 0 {
// When this condition executes ri and ti represent the
// r[i] and t[i] values such that i is the greatest
// index for which r >= sqrt(n). Meanwhile, the current
// r and t values are r[i+1] and t[i+1], respectively.
//
// a1 = r[i+1], b1 = -t[i+1]
a1.Set(r)
b1.Neg(t)
found = true
oneMore = true
// Skip to the next iteration so ri and ti are not
// modified.
continue
} else if oneMore {
// When this condition executes ri and ti still
// represent the r[i] and t[i] values while the current
// r and t are r[i+2] and t[i+2], respectively.
//
// sum1 = r[i]^2 + t[i]^2
rSquared := new(big.Int).Mul(ri, ri)
tSquared := new(big.Int).Mul(ti, ti)
sum1 := new(big.Int).Add(rSquared, tSquared)
// sum2 = r[i+2]^2 + t[i+2]^2
r2Squared := new(big.Int).Mul(r, r)
t2Squared := new(big.Int).Mul(t, t)
sum2 := new(big.Int).Add(r2Squared, t2Squared)
// if (r[i]^2 + t[i]^2) <= (r[i+2]^2 + t[i+2]^2)
if sum1.Cmp(sum2) <= 0 {
// a2 = r[i], b2 = -t[i]
a2.Set(ri)
b2.Neg(ti)
} else {
// a2 = r[i+2], b2 = -t[i+2]
a2.Set(r)
b2.Neg(t)
}
// All done.
break
}
ri.Set(r)
ti.Set(t)
}
return a1, b1, a2, b2
}
// deriveEndomorphismParams calculates and returns parameters needed to make use
// of the secp256k1 endomorphism.
func deriveEndomorphismParams() [2]endomorphismParams {
// roots returns the solutions of the characteristic polynomial of the
// secp256k1 endomorphism.
//
// The characteristic polynomial for the endomorphism is:
//
// X^2 + X + 1 ≡ 0 (mod n).
//
// Solving for X:
//
// 4X^2 + 4X + 4 ≡ 0 (mod n) | (*4, possible because gcd(4, n) = 1)
// (2X + 1)^2 + 3 ≡ 0 (mod n) | (factor by completing the square)
// (2X + 1)^2 ≡ -3 (mod n) | (-3)
// (2X + 1) ≡ ±sqrt(-3) (mod n) | (sqrt)
// 2X ≡ ±sqrt(-3) - 1 (mod n) | (-1)
// X ≡ (±sqrt(-3)-1)*2^-1 (mod n) | (*2^-1)
//
// So, the roots are:
// X1 ≡ (-(sqrt(-3)+1)*2^-1 (mod n)
// X2 ≡ (sqrt(-3)-1)*2^-1 (mod n)
//
// It is also worth noting that X is a cube root of unity, meaning
// X^3 - 1 ≡ 0 (mod n), hence it can be factored as (X - 1)(X^2 + X + 1) ≡ 0
// and (X1)^2 ≡ X2 (mod n) and (X2)^2 ≡ X1 (mod n).
roots := func(prime *big.Int) [2]big.Int {
var result [2]big.Int
one := big.NewInt(1)
twoInverse := new(big.Int).ModInverse(big.NewInt(2), prime)
negThree := new(big.Int).Neg(big.NewInt(3))
sqrtNegThree := new(big.Int).ModSqrt(negThree, prime)
sqrtNegThreePlusOne := new(big.Int).Add(sqrtNegThree, one)
negSqrtNegThreePlusOne := new(big.Int).Neg(sqrtNegThreePlusOne)
result[0].Mul(negSqrtNegThreePlusOne, twoInverse)
result[0].Mod(&result[0], prime)
sqrtNegThreeMinusOne := new(big.Int).Sub(sqrtNegThree, one)
result[1].Mul(sqrtNegThreeMinusOne, twoInverse)
result[1].Mod(&result[1], prime)
return result
}
// Find the λ's and β's which are the solutions for the characteristic
// polynomial of the secp256k1 endomorphism modulo the curve order and
// modulo the field order, respectively.
lambdas := roots(curveParams.N)
betas := roots(curveParams.P)
// Ensure the calculated roots are actually the roots of the characteristic
// polynomial.
checkRoots := func(foundRoots [2]big.Int, prime *big.Int) {
// X^2 + X + 1 ≡ 0 (mod p)
one := big.NewInt(1)
for i := 0; i < len(foundRoots); i++ {
root := &foundRoots[i]
result := new(big.Int).Mul(root, root)
result.Add(result, root)
result.Add(result, one)
result.Mod(result, prime)
if result.Sign() != 0 {
panic(
fmt.Sprintf(
"%[1]x^2 + %[1]x + 1 != 0 (mod %x)", root,
prime,
),
)
}
}
}
checkRoots(lambdas, curveParams.N)
checkRoots(betas, curveParams.P)
// checkVectors ensures the passed vectors satisfy the equation:
// a + b*λ ≡ 0 (mod n)
checkVectors := func(a, b *big.Int, lambda *big.Int) {
result := new(big.Int).Mul(b, lambda)
result.Add(result, a)
result.Mod(result, curveParams.N)
if result.Sign() != 0 {
panic(
fmt.Sprintf(
"%x + %x*lambda != 0 (mod %x)", a, b,
curveParams.N,
),
)
}
}
var endoParams [2]endomorphismParams
for i := 0; i < 2; i++ {
// Calculate the linearly independent vectors needed to generate a
// balanced length-two representation of a scalar such that
// k = k1 + k2*λ (mod n) for each of the solutions.
lambda := &lambdas[i]
a1, b1, a2, b2 := endomorphismVectors(lambda)
// Ensure the derived vectors satisfy the required equation.
checkVectors(a1, b1, lambda)
checkVectors(a2, b2, lambda)
// Calculate the precomputed estimates also used when generating the
// aforementioned decomposition.
//
// z1 = floor(b2<<320 / n)
// z2 = floor(((-b1)%n)<<320) / n)
const shift = 320
z1 := new(big.Int).Lsh(b2, shift)
z1.Div(z1, curveParams.N)
z2 := new(big.Int).Neg(b1)
z2.Lsh(z2, shift)
z2.Div(z2, curveParams.N)
params := &endoParams[i]
params.lambda = lambda
params.beta = &betas[i]
params.a1 = a1
params.b1 = b1
params.a2 = a2
params.b2 = b2
params.z1 = z1
params.z2 = z2
}
return endoParams
}
func main() {
if _, err := os.Stat(".git"); chk.T(err) {
fmt.Printf("File exists\n")
_, _ = fmt.Fprintln(
os.Stderr,
"This generator must be run with working directory at the root of"+
" the repository",
)
os.Exit(1)
}
serialized := serializedBytePoints()
embedded, err := os.Create("secp256k1/rawbytepoints.bin")
if err != nil {
log.F.Ln(err)
os.Exit(1)
}
n, err := embedded.Write(serialized)
if err != nil {
panic(err)
}
if n != len(serialized) {
fmt.Printf(
"failed to write all of byte points, wrote %d expected %d",
n, len(serialized),
)
panic("fail")
}
_ = embedded.Close()
}

View File

@@ -0,0 +1,234 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
// References:
// [SEC1] Elliptic Curve Cryptography
// https://www.secg.org/sec1-v2.pdf
//
// [SEC2] Recommended Elliptic Curve Domain Parameters
// https://www.secg.org/sec2-v2.pdf
//
// [ANSI X9.62-1998] Public Key Cryptography For The Financial Services
// Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA)
import (
"fmt"
)
const (
// PubKeyBytesLenCompressed is the number of bytes of a serialized
// compressed public key.
PubKeyBytesLenCompressed = 33
// PubKeyBytesLenUncompressed is the number of bytes of a serialized
// uncompressed public key.
PubKeyBytesLenUncompressed = 65
// PubKeyFormatCompressedEven is the identifier prefix byte for a public key
// whose Y coordinate is even when serialized in the compressed format per
// section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4).
PubKeyFormatCompressedEven byte = 0x02
// PubKeyFormatCompressedOdd is the identifier prefix byte for a public key
// whose Y coordinate is odd when serialized in the compressed format per
// section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4).
PubKeyFormatCompressedOdd byte = 0x03
// PubKeyFormatUncompressed is the identifier prefix byte for a public key
// when serialized according in the uncompressed format per section 2.3.3 of
// [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.3).
PubKeyFormatUncompressed byte = 0x04
// PubKeyFormatHybridEven is the identifier prefix byte for a public key
// whose Y coordinate is even when serialized according to the hybrid format
// per section 4.3.6 of [ANSI X9.62-1998].
//
// NOTE: This format makes little sense in practice an therefore this
// package will not produce public keys serialized in this format. However,
// it will parse them since they exist in the wild.
PubKeyFormatHybridEven byte = 0x06
// PubKeyFormatHybridOdd is the identifier prefix byte for a public key
// whose Y coordingate is odd when serialized according to the hybrid format
// per section 4.3.6 of [ANSI X9.62-1998].
//
// NOTE: This format makes little sense in practice an therefore this
// package will not produce public keys serialized in this format. However,
// it will parse them since they exist in the wild.
PubKeyFormatHybridOdd byte = 0x07
)
// PublicKey provides facilities for efficiently working with secp256k1 public
// keys within this package and includes functions to serialize in both
// uncompressed and compressed SEC (Standards for Efficient Cryptography)
// formats.
type PublicKey struct {
x FieldVal
y FieldVal
}
// NewPublicKey instantiates a new public key with the given x and y
// coordinates.
//
// It should be noted that, unlike ParsePubKey, since this accepts arbitrary x
// and y coordinates, it allows creation of public keys that are not valid
// points on the secp256k1 curve. The IsOnCurve method of the returned instance
// can be used to determine validity.
func NewPublicKey(x, y *FieldVal) *PublicKey {
var pubKey PublicKey
pubKey.x.Set(x)
pubKey.y.Set(y)
return &pubKey
}
// ParsePubKey parses a secp256k1 public key encoded according to the format
// specified by ANSI X9.62-1998, which means it is also compatible with the
// SEC (Standards for Efficient Cryptography) specification which is a subset of
// the former. In other words, it supports the uncompressed, compressed, and
// hybrid formats as follows:
//
// Compressed:
//
// <format byte = 0x02/0x03><32-byte X coordinate>
//
// Uncompressed:
//
// <format byte = 0x04><32-byte X coordinate><32-byte Y coordinate>
//
// Hybrid:
//
// <format byte = 0x05/0x06><32-byte X coordinate><32-byte Y coordinate>
//
// NOTE: The hybrid format makes little sense in practice an therefore this
// package will not produce public keys serialized in this format. However,
// this function will properly parse them since they exist in the wild.
func ParsePubKey(serialized []byte) (key *PublicKey, err error) {
var x, y FieldVal
switch len(serialized) {
case PubKeyBytesLenUncompressed:
// Reject unsupported public key formats for the given length.
format := serialized[0]
switch format {
case PubKeyFormatUncompressed:
case PubKeyFormatHybridEven, PubKeyFormatHybridOdd:
default:
str := fmt.Sprintf(
"invalid public key: unsupported format: %x",
format,
)
return nil, makeError(ErrPubKeyInvalidFormat, str)
}
// Parse the x and y coordinates while ensuring that they are in the
// allowed range.
if overflow := x.SetByteSlice(serialized[1:33]); overflow {
str := "invalid public key: x >= field prime"
return nil, makeError(ErrPubKeyXTooBig, str)
}
if overflow := y.SetByteSlice(serialized[33:]); overflow {
str := "invalid public key: y >= field prime"
return nil, makeError(ErrPubKeyYTooBig, str)
}
// Ensure the oddness of the y coordinate matches the specified format
// for hybrid public keys.
if format == PubKeyFormatHybridEven || format == PubKeyFormatHybridOdd {
wantOddY := format == PubKeyFormatHybridOdd
if y.IsOdd() != wantOddY {
str := fmt.Sprintf(
"invalid public key: y oddness does not "+
"match specified value of %v", wantOddY,
)
return nil, makeError(ErrPubKeyMismatchedOddness, str)
}
}
// Reject public keys that are not on the secp256k1 curve.
if !isOnCurve(&x, &y) {
str := fmt.Sprintf(
"invalid public key: [%v,%v] not on secp256k1 "+
"curve", x, y,
)
return nil, makeError(ErrPubKeyNotOnCurve, str)
}
case PubKeyBytesLenCompressed:
// Reject unsupported public key formats for the given length.
format := serialized[0]
switch format {
case PubKeyFormatCompressedEven, PubKeyFormatCompressedOdd:
default:
str := fmt.Sprintf(
"invalid public key: unsupported format: %x",
format,
)
return nil, makeError(ErrPubKeyInvalidFormat, str)
}
// Parse the x coordinate while ensuring that it is in the allowed
// range.
if overflow := x.SetByteSlice(serialized[1:33]); overflow {
str := "invalid public key: x >= field prime"
return nil, makeError(ErrPubKeyXTooBig, str)
}
// Attempt to calculate the y coordinate for the given x coordinate such
// that the result pair is a point on the secp256k1 curve and the
// solution with desired oddness is chosen.
wantOddY := format == PubKeyFormatCompressedOdd
if !DecompressY(&x, wantOddY, &y) {
str := fmt.Sprintf(
"invalid public key: x coordinate %v is not on "+
"the secp256k1 curve", x,
)
return nil, makeError(ErrPubKeyNotOnCurve, str)
}
y.Normalize()
default:
str := fmt.Sprintf(
"malformed public key: invalid length: %d",
len(serialized),
)
return nil, makeError(ErrPubKeyInvalidLen, str)
}
return NewPublicKey(&x, &y), nil
}
// SerializeUncompressed serializes a public key in the 65-byte uncompressed
// format.
func (p PublicKey) SerializeUncompressed() []byte {
// 0x04 || 32-byte x coordinate || 32-byte y coordinate
var b [PubKeyBytesLenUncompressed]byte
b[0] = PubKeyFormatUncompressed
p.x.PutBytesUnchecked(b[1:33])
p.y.PutBytesUnchecked(b[33:65])
return b[:]
}
// SerializeCompressed serializes a public key in the 33-byte compressed format.
func (p PublicKey) SerializeCompressed() []byte {
// Choose the format byte depending on the oddness of the Y coordinate.
format := PubKeyFormatCompressedEven
if p.y.IsOdd() {
format = PubKeyFormatCompressedOdd
}
// 0x02 or 0x03 || 32-byte x coordinate
var b [PubKeyBytesLenCompressed]byte
b[0] = format
p.x.PutBytesUnchecked(b[1:33])
return b[:]
}
// IsEqual compares this public key instance to the one passed, returning true
// if both public keys are equivalent. A public key is equivalent to another,
// if they both have the same X and Y coordinates.
func (p *PublicKey) IsEqual(otherPubKey *PublicKey) bool {
return p.x.Equals(&otherPubKey.x) && p.y.Equals(&otherPubKey.y)
}
// AsJacobian converts the public key into a Jacobian point with Z=1 and stores
// the result in the provided result param. This allows the public key to be
// treated a Jacobian point in the secp256k1 group in calculations.
func (p *PublicKey) AsJacobian(result *JacobianPoint) {
result.X.Set(&p.x)
result.Y.Set(&p.y)
result.Z.SetInt(1)
}
// IsOnCurve returns whether or not the public key represents a point on the
// secp256k1 curve.
func (p *PublicKey) IsOnCurve() bool {
return isOnCurve(&p.x, &p.y)
}

View File

@@ -0,0 +1,493 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"errors"
"testing"
"next.orly.dev/pkg/utils"
)
// TestParsePubKey ensures that public keys are properly parsed according
// to the spec including both the positive and negative cases.
func TestParsePubKey(t *testing.T) {
tests := []struct {
name string // test description
key string // hex encoded public key
err error // expected error
wantX string // expected x coordinate
wantY string // expected y coordinate
}{
{
name: "uncompressed ok",
key: "04" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
err: nil,
wantX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
wantY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
}, {
name: "uncompressed x changed (not on curve)",
key: "04" +
"15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
err: ErrPubKeyNotOnCurve,
}, {
name: "uncompressed y changed (not on curve)",
key: "04" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
err: ErrPubKeyNotOnCurve,
}, {
name: "uncompressed claims compressed",
key: "03" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
err: ErrPubKeyInvalidFormat,
}, {
name: "uncompressed as hybrid ok (ybit = 0)",
key: "06" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
err: nil,
wantX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
wantY: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
}, {
name: "uncompressed as hybrid ok (ybit = 1)",
key: "07" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
err: nil,
wantX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
wantY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
}, {
name: "uncompressed as hybrid wrong oddness",
key: "06" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
err: ErrPubKeyMismatchedOddness,
}, {
name: "compressed ok (ybit = 0)",
key: "02" +
"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
err: nil,
wantX: "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
wantY: "0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032",
}, {
name: "compressed ok (ybit = 1)",
key: "03" +
"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
err: nil,
wantX: "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
wantY: "499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f",
}, {
name: "compressed claims uncompressed (ybit = 0)",
key: "04" +
"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
err: ErrPubKeyInvalidFormat,
}, {
name: "compressed claims uncompressed (ybit = 1)",
key: "04" +
"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
err: ErrPubKeyInvalidFormat,
}, {
name: "compressed claims hybrid (ybit = 0)",
key: "06" +
"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
err: ErrPubKeyInvalidFormat,
}, {
name: "compressed claims hybrid (ybit = 1)",
key: "07" +
"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
err: ErrPubKeyInvalidFormat,
}, {
name: "compressed with invalid x coord (ybit = 0)",
key: "03" +
"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4c",
err: ErrPubKeyNotOnCurve,
}, {
name: "compressed with invalid x coord (ybit = 1)",
key: "03" +
"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448d",
err: ErrPubKeyNotOnCurve,
}, {
name: "empty",
key: "",
err: ErrPubKeyInvalidLen,
}, {
name: "wrong length",
key: "05",
err: ErrPubKeyInvalidLen,
}, {
name: "uncompressed x == p",
key: "04" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
err: ErrPubKeyXTooBig,
}, {
// The y coordinate produces a valid point for x == 1 (mod p), but it
// should fail to parse instead of wrapping around.
name: "uncompressed x > p (p + 1 -- aka 1)",
key: "04" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30" +
"bde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441",
err: ErrPubKeyXTooBig,
}, {
name: "uncompressed y == p",
key: "04" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
err: ErrPubKeyYTooBig,
}, {
// The x coordinate produces a valid point for y == 1 (mod p), but it
// should fail to parse instead of wrapping around.
name: "uncompressed y > p (p + 1 -- aka 1)",
key: "04" +
"1fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30",
err: ErrPubKeyYTooBig,
}, {
name: "compressed x == p (ybit = 0)",
key: "02" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
err: ErrPubKeyXTooBig,
}, {
name: "compressed x == p (ybit = 1)",
key: "03" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
err: ErrPubKeyXTooBig,
}, {
// This would be valid for x == 2 (mod p), but it should fail to parse
// instead of wrapping around.
name: "compressed x > p (p + 2 -- aka 2) (ybit = 0)",
key: "02" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc31",
err: ErrPubKeyXTooBig,
}, {
// This would be valid for x == 1 (mod p), but it should fail to parse
// instead of wrapping around.
name: "compressed x > p (p + 1 -- aka 1) (ybit = 1)",
key: "03" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30",
err: ErrPubKeyXTooBig,
}, {
name: "hybrid x == p (ybit = 1)",
key: "07" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
err: ErrPubKeyXTooBig,
}, {
// The y coordinate produces a valid point for x == 1 (mod p), but it
// should fail to parse instead of wrapping around.
name: "hybrid x > p (p + 1 -- aka 1) (ybit = 0)",
key: "06" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30" +
"bde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441",
err: ErrPubKeyXTooBig,
}, {
name: "hybrid y == p (ybit = 0 when mod p)",
key: "06" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
err: ErrPubKeyYTooBig,
}, {
// The x coordinate produces a valid point for y == 1 (mod p), but it
// should fail to parse instead of wrapping around.
name: "hybrid y > p (p + 1 -- aka 1) (ybit = 1 when mod p)",
key: "07" +
"1fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507" +
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc30",
err: ErrPubKeyYTooBig,
},
}
for _, test := range tests {
pubKeyBytes := hexToBytes(test.key)
pubKey, err := ParsePubKey(pubKeyBytes)
if !errors.Is(err, test.err) {
t.Errorf(
"%s mismatched e -- got %v, want %v", test.name, err,
test.err,
)
continue
}
if err != nil {
continue
}
// Ensure the x and y coordinates match the expected values upon
// successful parse.
wantX, wantY := hexToFieldVal(test.wantX), hexToFieldVal(test.wantY)
if !pubKey.x.Equals(wantX) {
t.Errorf(
"%s: mismatched x coordinate -- got %v, want %v",
test.name, pubKey.x, wantX,
)
continue
}
if !pubKey.y.Equals(wantY) {
t.Errorf(
"%s: mismatched y coordinate -- got %v, want %v",
test.name, pubKey.y, wantY,
)
continue
}
}
}
// TestPubKeySerialize ensures that serializing public keys works as expected
// for both the compressed and uncompressed cases.
func TestPubKeySerialize(t *testing.T) {
tests := []struct {
name string // test description
pubX string // hex encoded x coordinate for pubkey to serialize
pubY string // hex encoded y coordinate for pubkey to serialize
compress bool // whether to serialize compressed or uncompressed
expected string // hex encoded expected pubkey serialization
}{
{
name: "uncompressed (ybit = 0)",
pubX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
compress: false,
expected: "04" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
}, {
name: "uncompressed (ybit = 1)",
pubX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
compress: false,
expected: "04" +
"11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
}, {
// It's invalid to parse pubkeys that are not on the curve, however it
// is possible to manually create them and they should serialize
// correctly.
name: "uncompressed not on the curve due to x coord",
pubX: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
compress: false,
expected: "04" +
"15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
}, {
// It's invalid to parse pubkeys that are not on the curve, however it
// is possible to manually create them and they should serialize
// correctly.
name: "uncompressed not on the curve due to y coord",
pubX: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
compress: false,
expected: "04" +
"15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c" +
"b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
}, {
name: "compressed (ybit = 0)",
pubX: "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
pubY: "0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032",
compress: true,
expected: "02" +
"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d",
}, {
name: "compressed (ybit = 1)",
pubX: "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
pubY: "499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f",
compress: true,
expected: "03" +
"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e",
}, {
// It's invalid to parse pubkeys that are not on the curve, however it
// is possible to manually create them and they should serialize
// correctly.
name: "compressed not on curve (ybit = 0)",
pubX: "ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4c",
pubY: "0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032",
compress: true,
expected: "02" +
"ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4c",
}, {
// It's invalid to parse pubkeys that are not on the curve, however it
// is possible to manually create them and they should serialize
// correctly.
name: "compressed not on curve (ybit = 1)",
pubX: "2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448d",
pubY: "499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f",
compress: true,
expected: "03" +
"2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448d",
},
}
for _, test := range tests {
// Parse the test data.
x, y := hexToFieldVal(test.pubX), hexToFieldVal(test.pubY)
pubKey := NewPublicKey(x, y)
// Serialize with the correct method and ensure the result matches the
// expected value.
var serialized []byte
if test.compress {
serialized = pubKey.SerializeCompressed()
} else {
serialized = pubKey.SerializeUncompressed()
}
expected := hexToBytes(test.expected)
if !utils.FastEqual(serialized, expected) {
t.Errorf(
"%s: mismatched serialized public key -- got %x, want %x",
test.name, serialized, expected,
)
continue
}
}
}
// TestPublicKeyIsEqual ensures that equality testing between two public keys
// works as expected.
func TestPublicKeyIsEqual(t *testing.T) {
pubKey1 := &PublicKey{
x: *hexToFieldVal("2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e"),
y: *hexToFieldVal("499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f"),
}
pubKey1Copy := &PublicKey{
x: *hexToFieldVal("2689c7c2dab13309fb143e0e8fe396342521887e976690b6b47f5b2a4b7d448e"),
y: *hexToFieldVal("499dd7852849a38aa23ed9f306f07794063fe7904e0f347bc209fdddaf37691f"),
}
pubKey2 := &PublicKey{
x: *hexToFieldVal("ce0b14fb842b1ba549fdd675c98075f12e9c510f8ef52bd021a9a1f4809d3b4d"),
y: *hexToFieldVal("0890ff84d7999d878a57bee170e19ef4b4803b4bdede64503a6ac352b03c8032"),
}
if !pubKey1.IsEqual(pubKey1) {
t.Fatalf(
"bad self public key equality check: (%v, %v)", pubKey1.x,
pubKey1.y,
)
}
if !pubKey1.IsEqual(pubKey1Copy) {
t.Fatalf(
"bad public key equality check: (%v, %v) == (%v, %v)",
pubKey1.x, pubKey1.y, pubKey1Copy.x, pubKey1Copy.y,
)
}
if pubKey1.IsEqual(pubKey2) {
t.Fatalf(
"bad public key equality check: (%v, %v) != (%v, %v)",
pubKey1.x, pubKey1.y, pubKey2.x, pubKey2.y,
)
}
}
// TestPublicKeyAsJacobian ensures converting a public key to a jacobian point
// with a Z coordinate of 1 works as expected.
func TestPublicKeyAsJacobian(t *testing.T) {
tests := []struct {
name string // test description
pubKey string // hex encoded serialized compressed pubkey
wantX string // hex encoded expected X coordinate
wantY string // hex encoded expected Y coordinate
}{
{
name: "public key for secret key 0x01",
pubKey: "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
wantX: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
wantY: "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
}, {
name: "public for secret key 0x03",
pubKey: "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
wantX: "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
wantY: "388f7b0f632de8140fe337e62a37f3566500a99934c2231b6cb9fd7584b8e672",
}, {
name: "public for secret key 0x06",
pubKey: "03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
wantX: "fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556",
wantY: "ae12777aacfbb620f3be96017f45c560de80f0f6518fe4a03c870c36b075f297",
},
}
for _, test := range tests {
// Parse the test data.
pubKeyBytes := hexToBytes(test.pubKey)
wantX := hexToFieldVal(test.wantX)
wantY := hexToFieldVal(test.wantY)
pubKey, err := ParsePubKey(pubKeyBytes)
if err != nil {
t.Errorf("%s: failed to parse public key: %v", test.name, err)
continue
}
// Convert the public key to a jacobian point and ensure the coordinates
// match the expected values.
var point JacobianPoint
pubKey.AsJacobian(&point)
if !point.Z.IsOne() {
t.Errorf(
"%s: invalid Z coordinate -- got %v, want 1", test.name,
point.Z,
)
continue
}
if !point.X.Equals(wantX) {
t.Errorf(
"%s: invalid X coordinate - got %v, want %v", test.name,
point.X, wantX,
)
continue
}
if !point.Y.Equals(wantY) {
t.Errorf(
"%s: invalid Y coordinate - got %v, want %v", test.name,
point.Y, wantY,
)
continue
}
}
}
// TestPublicKeyIsOnCurve ensures testing if a public key is on the curve works
// as expected.
func TestPublicKeyIsOnCurve(t *testing.T) {
tests := []struct {
name string // test description
pubX string // hex encoded x coordinate for pubkey to serialize
pubY string // hex encoded y coordinate for pubkey to serialize
want bool // expected result
}{
{
name: "valid with even y",
pubX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "4d1f1522047b33068bbb9b07d1e9f40564749b062b3fc0666479bc08a94be98c",
want: true,
}, {
name: "valid with odd y",
pubX: "11db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
want: true,
}, {
name: "invalid due to x coord",
pubX: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3",
want: false,
}, {
name: "invalid due to y coord",
pubX: "15db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c",
pubY: "b2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a4",
want: false,
},
}
for _, test := range tests {
// Parse the test data.
x, y := hexToFieldVal(test.pubX), hexToFieldVal(test.pubY)
pubKey := NewPublicKey(x, y)
result := pubKey.IsOnCurve()
if result != test.want {
t.Errorf(
"%s: mismatched is on curve result -- got %v, want %v",
test.name, result, test.want,
)
continue
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,130 @@
// Copyright (c) 2013-2014 The btcsuite developers
// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"crypto/rand"
"io"
"lol.mleku.dev/chk"
)
// SecretKey provides facilities for working with secp256k1 secret keys within
// this package and includes functionality such as serializing and parsing them
// as well as computing their associated public key.
type SecretKey struct {
Key ModNScalar
}
// PrivateKey is a secret key.
//
// Deprecated: use SecretKey - secret = one person; private = two or more (you don't share secret keys!)
type PrivateKey = SecretKey
// NewSecretKey instantiates a new secret key from a scalar encoded as a big integer.
func NewSecretKey(key *ModNScalar) *SecretKey { return &SecretKey{Key: *key} }
// NewPrivateKey instantiates a new secret key from a scalar encoded as a big integer.
//
// Deprecated: use NewSecretKey - secret = one person; private = two or more (you don't share secret keys!)
var NewPrivateKey = NewSecretKey
// SecKeyFromBytes returns a secret based on the provided byte slice which is
// interpreted as an unsigned 256-bit big-endian integer in the range [0, N-1],
// where N is the order of the curve.
//
// WARNING: This means passing a slice with more than 32 bytes is truncated and
// that truncated value is reduced modulo N. Further, 0 is not a valid secret
// key. It is up to the caller to provide a value in the appropriate range of
// [1, N-1]. Failure to do so will either result in an invalid secret key or
// potentially weak secret keys that have bias that could be exploited.
//
// This function primarily exists to provide a mechanism for converting
// serialized secret keys that are already known to be good.
//
// Typically, callers should make use of GenerateSecretKey or
// GenerateSecretKeyFromRand when creating secret keys since they properly
// handle generation of appropriate values.
func SecKeyFromBytes(secKeyBytes []byte) *SecretKey {
var secKey SecretKey
secKey.Key.SetByteSlice(secKeyBytes)
return &secKey
}
var PrivKeyFromBytes = SecKeyFromBytes
// generateSecretKey generates and returns a new secret key that is suitable
// for use with secp256k1 using the provided reader as a source of entropy. The
// provided reader must be a source of cryptographically secure randomness to
// avoid weak secret keys.
func generateSecretKey(rand io.Reader) (*SecretKey, error) {
// The group order is close enough to 2^256 that there is only roughly a 1
// in 2^128 chance of generating an invalid secret key, so this loop will
// virtually never run more than a single iteration in practice.
var key SecretKey
var b32 [32]byte
for valid := false; !valid; {
if _, err := io.ReadFull(rand, b32[:]); chk.T(err) {
return nil, err
}
// The secret key is only valid when it is in the range [1, N-1], where
// N is the order of the curve.
overflow := key.Key.SetBytes(&b32)
valid = (key.Key.IsZeroBit() | overflow) == 0
}
zeroArray32(&b32)
return &key, nil
}
// GenerateSecretKey generates and returns a new cryptographically secure secret key that is suitable for use with
// secp256k1.
func GenerateSecretKey() (*SecretKey, error) {
return generateSecretKey(rand.Reader)
}
// GeneratePrivateKey generates and returns a new cryptographically secure secret key that is suitable for use with
// secp256k1.
//
// Deprecated: use NewSecretKey - secret = one person; private = two or more (you don't share secret keys!)
var GeneratePrivateKey = GenerateSecretKey
// GenerateSecretKeyFromRand generates a secret key that is suitable for use with secp256k1 using the provided reader as
// a source of entropy. The provided reader must be a source of cryptographically secure randomness, such as
// [crypto/rand.Reader], to avoid weak secret keys.
func GenerateSecretKeyFromRand(rand io.Reader) (*SecretKey, error) {
return generateSecretKey(rand)
}
// GeneratePrivateKeyFromRand generates a secret key that is suitable for use with secp256k1 using the provided reader as
// a source of entropy. The provided reader must be a source of cryptographically secure randomness, such as
// [crypto/rand.Reader], to avoid weak secret keys.
//
// Deprecated: use GenerateSecretKeyFromRand - secret = one person; private = two or more (you don't share secret keys!)
var GeneratePrivateKeyFromRand = GenerateSecretKeyFromRand
// PubKey computes and returns the public key corresponding to this secret key.
func (p *SecretKey) PubKey() *PublicKey {
var result JacobianPoint
ScalarBaseMultNonConst(&p.Key, &result)
result.ToAffine()
return NewPublicKey(&result.X, &result.Y)
}
// Zero manually clears the memory associated with the secret key. This can be
// used to explicitly clear key material from memory for enhanced security
// against memory scraping.
func (p *SecretKey) Zero() { p.Key.Zero() }
// SecKeyBytesLen defines the length in bytes of a serialized secret key.
const SecKeyBytesLen = 32
// Serialize returns the secret key as a 256-bit big-endian binary-encoded
// number, padded to a length of 32 bytes.
func (p *SecretKey) Serialize() []byte {
var secKeyBytes [SecKeyBytesLen]byte
p.Key.PutBytes(&secKeyBytes)
return secKeyBytes[:]
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"testing"
)
// BenchmarkSecretKeyGenerate benchmarks generating new cryptographically
// secure secret keys.
func BenchmarkSecretKeyGenerate(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := GenerateSecretKey()
if err != nil {
b.Fatal(err)
}
}
}

View File

@@ -0,0 +1,179 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package secp256k1
import (
"bytes"
"crypto/rand"
"errors"
"math/big"
"testing"
"next.orly.dev/pkg/utils"
)
// TestGenerateSecretKey ensures the key generation works as expected.
func TestGenerateSecretKey(t *testing.T) {
sec, err := GenerateSecretKey()
if err != nil {
t.Errorf("failed to generate secret key: %s", err)
return
}
pub := sec.PubKey()
if !isOnCurve(&pub.x, &pub.y) {
t.Error("public key is not on the curve")
}
}
// TestGenerateSecretKeyFromRand ensures generating a secret key from a random
// entropy source works as expected.
func TestGenerateSecretKeyFromRand(t *testing.T) {
sec, err := GenerateSecretKeyFromRand(rand.Reader)
if err != nil {
t.Errorf("failed to generate secret key: %s", err)
return
}
pub := sec.PubKey()
if !isOnCurve(&pub.x, &pub.y) {
t.Error("public key is not on the curve")
}
}
// mockSecretKeyReaderFunc is an adapter to allow the use of an ordinary
// function as an io.Reader.
type mockSecretKeyReaderFunc func([]byte) (int, error)
// Read calls the function with the provided parameter and returns the result.
func (f mockSecretKeyReaderFunc) Read(p []byte) (int, error) {
return f(p)
}
// TestGenerateSecretKeyCorners ensures random values that secret key
// generation correctly handles entropy values that are invalid for use as
// secret keys by creating a fake source of randomness to inject known bad
// values.
func TestGenerateSecretKeyCorners(t *testing.T) {
// Create a mock reader that returns the following sequence of values:
// 1st invocation: 0
// 2nd invocation: The curve order
// 3rd invocation: The curve order + 1
// 4th invocation: 1 (32-byte big endian)
oneModN := hexToModNScalar("01")
var numReads int
mockReader := mockSecretKeyReaderFunc(
func(p []byte) (int, error) {
numReads++
switch numReads {
case 1:
return copy(p, bytes.Repeat([]byte{0x00}, len(p))), nil
case 2:
return copy(p, curveParams.N.Bytes()), nil
case 3:
nPlusOne := new(big.Int).Add(curveParams.N, big.NewInt(1))
return copy(p, nPlusOne.Bytes()), nil
}
oneModNBytes := oneModN.Bytes()
return copy(p, oneModNBytes[:]), nil
},
)
// Generate a secret key using the mock reader and ensure the resulting key
// is the expected one. It should be the value "1" since the other values
// the sequence produces are invalid and thus should be rejected.
sec, err := GenerateSecretKeyFromRand(mockReader)
if err != nil {
t.Errorf("failed to generate secret key: %s", err)
return
}
if !sec.Key.Equals(oneModN) {
t.Fatalf(
"unexpected secret key -- got: %x, want %x", sec.Serialize(),
oneModN.Bytes(),
)
}
}
// TestGenerateSecretKeyError ensures the secret key generation properly
// handles errors when attempting to read from the source of randomness.
func TestGenerateSecretKeyError(t *testing.T) {
// Create a mock reader that returns an error.
errDisabled := errors.New("disabled")
mockReader := mockSecretKeyReaderFunc(
func(p []byte) (int, error) {
return 0, errDisabled
},
)
// Generate a secret key using the mock reader and ensure the expected
// error is returned.
_, err := GenerateSecretKeyFromRand(mockReader)
if !errors.Is(err, errDisabled) {
t.Fatalf("mismatched err -- got %v, want %v", err, errDisabled)
return
}
}
// TestSecKeys ensures a secret key created from bytes produces both the
// correct associated public key as well serializes back to the original bytes.
func TestSecKeys(t *testing.T) {
tests := []struct {
name string
sec string // hex encoded secret key to test
pub string // expected hex encoded serialized compressed public key
}{
{
name: "random secret key 1",
sec: "eaf02ca348c524e6392655ba4d29603cd1a7347d9d65cfe93ce1ebffdca22694",
pub: "025ceeba2ab4a635df2c0301a3d773da06ac5a18a7c3e0d09a795d7e57d233edf1",
}, {
name: "random secret key 2",
sec: "24b860d0651db83feba821e7a94ba8b87162665509cefef0cbde6a8fbbedfe7c",
pub: "032a6e51bf218085647d330eac2fafaeee07617a777ad9e8e7141b4cdae92cb637",
},
}
for _, test := range tests {
// Parse test data.
secKeyBytes := hexToBytes(test.sec)
wantPubKeyBytes := hexToBytes(test.pub)
sec := SecKeyFromBytes(secKeyBytes)
pub := sec.PubKey()
serializedPubKey := pub.SerializeCompressed()
if !utils.FastEqual(serializedPubKey, wantPubKeyBytes) {
t.Errorf(
"%s unexpected serialized public key - got: %x, want: %x",
test.name, serializedPubKey, wantPubKeyBytes,
)
}
serializedSecKey := sec.Serialize()
if !utils.FastEqual(serializedSecKey, secKeyBytes) {
t.Errorf(
"%s unexpected serialized secret key - got: %x, want: %x",
test.name, serializedSecKey, secKeyBytes,
)
}
}
}
// TestSecretKeyZero ensures that zeroing a secret key clears the memory
// associated with it.
func TestSecretKeyZero(t *testing.T) {
// Create a new secret key and zero the initial key material that is now
// copied into the secret key.
key := new(ModNScalar).SetHex("eaf02ca348c524e6392655ba4d29603cd1a7347d9d65cfe93ce1ebffdca22694")
secKey := NewSecretKey(key)
key.Zero()
// Ensure the secret key is non zero.
if secKey.Key.IsZero() {
t.Fatal("secret key is zero when it should be non zero")
}
// Zero the secret key and ensure it was properly zeroed.
secKey.Zero()
if !secKey.Key.IsZero() {
t.Fatal("secret key is non zero when it should be zero")
}
}