initial addition of essential crypto, encoders, workflows and LLM instructions
This commit is contained in:
23
pkg/crypto/ec/secp256k1/LICENSE
Normal file
23
pkg/crypto/ec/secp256k1/LICENSE
Normal 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.
|
||||
54
pkg/crypto/ec/secp256k1/README.md
Normal file
54
pkg/crypto/ec/secp256k1/README.md
Normal 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.
|
||||
177
pkg/crypto/ec/secp256k1/bench_test.go
Normal file
177
pkg/crypto/ec/secp256k1/bench_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
1222
pkg/crypto/ec/secp256k1/curve.go
Normal file
1222
pkg/crypto/ec/secp256k1/curve.go
Normal file
File diff suppressed because it is too large
Load Diff
1012
pkg/crypto/ec/secp256k1/curve_test.go
Normal file
1012
pkg/crypto/ec/secp256k1/curve_test.go
Normal file
File diff suppressed because it is too large
Load Diff
58
pkg/crypto/ec/secp256k1/doc.go
Normal file
58
pkg/crypto/ec/secp256k1/doc.go
Normal 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
|
||||
21
pkg/crypto/ec/secp256k1/ecdh.go
Normal file
21
pkg/crypto/ec/secp256k1/ecdh.go
Normal 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[:]
|
||||
}
|
||||
35
pkg/crypto/ec/secp256k1/ecdh_test.go
Normal file
35
pkg/crypto/ec/secp256k1/ecdh_test.go
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
247
pkg/crypto/ec/secp256k1/ellipticadaptor.go
Normal file
247
pkg/crypto/ec/secp256k1/ellipticadaptor.go
Normal 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
|
||||
}
|
||||
51
pkg/crypto/ec/secp256k1/ellipticadaptor_bench_test.go
Normal file
51
pkg/crypto/ec/secp256k1/ellipticadaptor_bench_test.go
Normal 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())
|
||||
}
|
||||
}
|
||||
427
pkg/crypto/ec/secp256k1/ellipticadaptor_test.go
Normal file
427
pkg/crypto/ec/secp256k1/ellipticadaptor_test.go
Normal 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
56
pkg/crypto/ec/secp256k1/error.go
Normal file
56
pkg/crypto/ec/secp256k1/error.go
Normal 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}
|
||||
}
|
||||
136
pkg/crypto/ec/secp256k1/error_test.go
Normal file
136
pkg/crypto/ec/secp256k1/error_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
132
pkg/crypto/ec/secp256k1/example_test.go
Normal file
132
pkg/crypto/ec/secp256k1/example_test.go
Normal 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
|
||||
}
|
||||
1615
pkg/crypto/ec/secp256k1/field.go
Normal file
1615
pkg/crypto/ec/secp256k1/field.go
Normal file
File diff suppressed because it is too large
Load Diff
92
pkg/crypto/ec/secp256k1/field_bench_test.go
Normal file
92
pkg/crypto/ec/secp256k1/field_bench_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
2018
pkg/crypto/ec/secp256k1/field_test.go
Normal file
2018
pkg/crypto/ec/secp256k1/field_test.go
Normal file
File diff suppressed because it is too large
Load Diff
88
pkg/crypto/ec/secp256k1/loadprecomputed.go
Normal file
88
pkg/crypto/ec/secp256k1/loadprecomputed.go
Normal 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
|
||||
}
|
||||
}()
|
||||
1055
pkg/crypto/ec/secp256k1/modnscalar.go
Normal file
1055
pkg/crypto/ec/secp256k1/modnscalar.go
Normal file
File diff suppressed because it is too large
Load Diff
274
pkg/crypto/ec/secp256k1/modnscalar_bench_test.go
Normal file
274
pkg/crypto/ec/secp256k1/modnscalar_bench_test.go
Normal 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()
|
||||
}
|
||||
}
|
||||
1323
pkg/crypto/ec/secp256k1/modnscalar_test.go
Normal file
1323
pkg/crypto/ec/secp256k1/modnscalar_test.go
Normal file
File diff suppressed because it is too large
Load Diff
251
pkg/crypto/ec/secp256k1/nonce.go
Normal file
251
pkg/crypto/ec/secp256k1/nonce.go
Normal 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()
|
||||
}
|
||||
}
|
||||
225
pkg/crypto/ec/secp256k1/nonce_test.go
Normal file
225
pkg/crypto/ec/secp256k1/nonce_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
22
pkg/crypto/ec/secp256k1/precomps.go
Normal file
22
pkg/crypto/ec/secp256k1/precomps.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
3
pkg/crypto/ec/secp256k1/precomps/doc.go
Normal file
3
pkg/crypto/ec/secp256k1/precomps/doc.go
Normal 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
|
||||
340
pkg/crypto/ec/secp256k1/precomps/genprecomps.go
Normal file
340
pkg/crypto/ec/secp256k1/precomps/genprecomps.go
Normal 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()
|
||||
}
|
||||
234
pkg/crypto/ec/secp256k1/pubkey.go
Normal file
234
pkg/crypto/ec/secp256k1/pubkey.go
Normal 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)
|
||||
}
|
||||
493
pkg/crypto/ec/secp256k1/pubkey_test.go
Normal file
493
pkg/crypto/ec/secp256k1/pubkey_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
pkg/crypto/ec/secp256k1/rawbytepoints.bin
Normal file
BIN
pkg/crypto/ec/secp256k1/rawbytepoints.bin
Normal file
Binary file not shown.
130
pkg/crypto/ec/secp256k1/seckey.go
Normal file
130
pkg/crypto/ec/secp256k1/seckey.go
Normal 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[:]
|
||||
}
|
||||
22
pkg/crypto/ec/secp256k1/seckey_bench_test.go
Normal file
22
pkg/crypto/ec/secp256k1/seckey_bench_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
179
pkg/crypto/ec/secp256k1/seckey_test.go
Normal file
179
pkg/crypto/ec/secp256k1/seckey_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user