diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4f66357..0ebd67f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: cache: true - name: run Go tidy run: make go-tidy - - name: Run golangci-lint with cgo + - name: Run golangci-lint env: CGO_ENABLED: 1 uses: golangci/golangci-lint-action@v8 @@ -42,21 +42,8 @@ jobs: version: ${{ env.LINT_VERSION }} # https://github.com/golangci/golangci-lint-action/issues/244 skip-cache: true - - name: Run golangci-lint without cgo - env: - CGO_ENABLED: 0 - uses: golangci/golangci-lint-action@v8 - with: - version: ${{ env.LINT_VERSION }} - args: --build-tags no_cgo - # https://github.com/golangci/golangci-lint-action/issues/244 - skip-cache: true - name: Run Go Fix run: make go-fix - - name: Run incorrect builds - run: | - echo "::remove-matcher owner=go::" - make incorrect_builds c-code: strategy: diff --git a/.golangci.yml b/.golangci.yml index d73fb864..ca737b81 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -10,17 +10,6 @@ linters: - linters: - govet text: "unsafeptr" # disable flagging unsafeptr usage - # ecdsa.go wraps crypto/ecdsa, whose Sign/Verify still consume the raw - # PrivateKey.D and PublicKey.X/Y fields. - # Go 1.26 deprecated direct access to those fields, but the recommended - # replacement API (ecdsa.ParseRawPrivateKey / ParseUncompressedPublicKey / - # (*PrivateKey).Bytes / (*PublicKey).Bytes) supports only the NIST curves and - # rejects secp256k1, which this package supports via btcec's custom - # elliptic.Curve, so the package has to keep using the low-level fields. - - path: (^|/)ecdsa(_test)?\.go$ - linters: - - staticcheck - text: "SA1019" formatters: exclusions: paths: diff --git a/Makefile b/Makefile index 1fdd5cd5..aba22695 100644 --- a/Makefile +++ b/Makefile @@ -96,15 +96,7 @@ go-lint: go-tidy go-fix test: # root package CGO_ENABLED=1 CGO_CFLAGS=$(ADX_FLAG) go test -coverprofile=$(COVER_PROFILE) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(VERBOSE),-v,) -#root package without cgo - CGO_ENABLED=0 go test -tags=no_cgo -coverprofile=$(COVER_PROFILE) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(VERBOSE),-v,) # sub packages go test -coverprofile=$(COVER_PROFILE) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(VERBOSE),-v,) ./hash go test -coverprofile=$(COVER_PROFILE) $(RACE_FLAG) $(if $(JSON_OUTPUT),-json,) $(if $(VERBOSE),-v,) ./random -# test incorrect builds and make sure they fail -.PHONY: incorrect_builds -incorrect_builds: -# both tests should fail - ! CGO_ENABLED=0 go test - ! CGO_ENABLED=1 CGO_CFLAGS=$(ADX_FLAG) go test -tags=no_cgo diff --git a/README.md b/README.md index 0c78ecac..65cd03e9 100644 --- a/README.md +++ b/README.md @@ -29,11 +29,6 @@ import "github.com/onflow/crypto" Building your project with Flow crypto and enabling all the supported algorithms requires using cgo to compile the C code underneath. If cgo isn't enabled by default, the `CGO_ENABLED` environment variable should be set to `1`. -It is also possible to build without cgo (`CGO_ENABLED=0`) but this would disable some primitives (the ones related to BLS). - -### Build with cgo - -Building with cgo is required to support all the algorithms of the module, including the algorithms based on the BLS12-381 curve. If the test or target application crashes with a "Caught SIGILL" exception, rebuild with `CGO_CFLAGS` set to `"-O2 -D__BLST_PORTABLE__"` to disable non-portable code. The runtime error can happen if the CPU doesn't support certain instructions. @@ -53,19 +48,6 @@ GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 go build When using the `go mod vendor` command in your project, [a known issue](https://github.com/golang/go/issues/26366) with the Go vendoring tool prevents cgo dependencies from being copied into your vendor directory. This results in build errors related to the Flow crypto package. External vendoring tools that do copy the entire package files can be used instead of the Go command to resolve the issue. - -### Build without cgo - -It is possible to build without cgo but this requires disabling all primitives based on the BLS12-381 curve (BLS signature, BLS threshold signature, BLS-based DKG, BLS-based SPoCK). -Refer to [algorithms](#algorithms) and [protocols](#protocols) to check the supported features. -Calling any of the non-supported primitives would panic. -In order to avoid accidental builds that result in unwanted crashes, disabling cgo must be confirmed with the `no_cgo` build tag. - -``` -CGO_ENABLED=0 go build -tags=no_cgo -``` - - ## Algorithms ### Hashing and MAC: @@ -83,10 +65,10 @@ All signature schemes use the generic interfaces of `PrivateKey` and `PublicKey` * ECDSA * public keys are compressed or uncompressed. - * ephemeral key is derived from the private key, hash and the system entropy (based on https://golang.org/pkg/crypto/ecdsa/). * supports NIST P-256 (secp256r1) and secp256k1 curves. + * For NIST P-256, ephemeral key is derived from the private key, hash and the system entropy (based on https://golang.org/pkg/crypto/ecdsa/). For secp256k1, ephemeral key is deterministically formed following RFC 6979 (based on github.com/ethereum/go-ethereum/crypto/secp256k1) - * BLS (requires cgo) + * BLS * supports [BLS12-381](https://electriccoin.co/blog/new-snark-curve/) curve. * is implementing the minimal-signature-size variant: signatures in G1 and public keys in G2. @@ -114,7 +96,7 @@ All signature schemes use the generic interfaces of `PrivateKey` and `PublicKey` ### Threshold Signature - * BLS-based threshold signature (requires cgo) + * BLS-based threshold signature * [non interactive](https://www.iacr.org/archive/pkc2003/25670031/25670031.pdf) threshold signature reconstruction. * supports only BLS 12-381 curve with the same features above. * (t+1) signatures are required to reconstruct the threshold signature. @@ -126,16 +108,16 @@ All signature schemes use the generic interfaces of `PrivateKey` and `PublicKey` All supported Distributed Key Generation protocols are [discrete log based](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.50.2737&rep=rep1&type=pdf) and are implemented for the same BLS setup on the BLS 12-381 curve. The protocols generate key sets for the BLS-based threshold signature. - * Feldman VSS (requires cgo) + * Feldman VSS * simple verifiable secret sharing with a single dealer. * the library does not implement the communication channels between participants. The caller should implement the methods `PrivateSend` (1-to-1 messaging) and `Broadcast` (1-to-n messaging) * 1-to-1 messaging must be a private channel, the caller must make sure the channel preserves confidentialiy and authenticates the sender. * 1-to-n broadcasting is a reliable broadcast, where honest senders are able to reach all honest receivers, and where all honest receivers end up with the same received messages. The channel should also authenticate the broadcaster. * It is recommended that both communication channels are unique per protocol instance. This could be achieved by prepending the messages to send/broadcast by a unique protocol instance ID. - * Feldman VSS Qual (requires cgo) + * Feldman VSS Qual * an extension of the simple Feldman VSS. * implements a complaint mechanism to qualify/disqualify the dealer. - * Joint Feldman (Pedersen) (requires cgo) + * Joint Feldman (Pedersen) * distributed generation. * based on parallel instances of Feldman VSS Qual, each with a different dealer. * same assumptions about the communication channels as in Feldman VSS. diff --git a/bls.go b/bls.go index e371c60e..e80465cf 100644 --- a/bls.go +++ b/bls.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/bls12381_utils.go b/bls12381_utils.go index e504a914..784f5389 100644 --- a/bls12381_utils.go +++ b/bls12381_utils.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * @@ -117,6 +115,10 @@ func initBLS12381() { // set a global point to infinity C.E2_set_infty((*C.E2)(&g2PublicKey.point)) g2PublicKey.isIdentity = true + + blsInstance = &blsBLS12381Algo{ + algo: BLSBLS12381, + } } // String returns a hex-encoded representation of the scalar. diff --git a/bls12381_utils_test.go b/bls12381_utils_test.go index f392be05..35a37025 100644 --- a/bls12381_utils_test.go +++ b/bls12381_utils_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/bls_crossBLST_test.go b/bls_crossBLST_test.go index b5f47f60..a0d04b60 100644 --- a/bls_crossBLST_test.go +++ b/bls_crossBLST_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/bls_multisig.go b/bls_multisig.go index dbb1625e..03f55e6e 100644 --- a/bls_multisig.go +++ b/bls_multisig.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/bls_test.go b/bls_test.go index f7e7893d..4fbf4fe4 100644 --- a/bls_test.go +++ b/bls_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/bls_thresholdsign.go b/bls_thresholdsign.go index c7de6761..f790df7f 100644 --- a/bls_thresholdsign.go +++ b/bls_thresholdsign.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/bls_thresholdsign_test.go b/bls_thresholdsign_test.go index a69155bf..f8c310f8 100644 --- a/bls_thresholdsign_test.go +++ b/bls_thresholdsign_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/blst_src/README.md b/blst_src/README.md index 48abe90e..1f4085f1 100644 --- a/blst_src/README.md +++ b/blst_src/README.md @@ -21,7 +21,7 @@ The folder contains: To upgrade the BLST version: - [ ] audit all BLST updates, with focus on `/src`: https://github.com/supranational/blst/compare/v0.3.14... - [ ] delete all files in this folder `./blst_src/` but `blst_src.c` and `README.md`. -- [ ] delete all files in `./internal/blst/` but `non_cgo.go`. +- [ ] delete all files in `./internal/blst/`. - [ ] open BLST repository on the new version. - [ ] copy all `.c` and `.h` files from `/src/` into `./blst_src/`. - [ ] delete newly copied `./blst_src/server.c`. diff --git a/dkg_feldmanvss.go b/dkg_feldmanvss.go index 696e9050..959da868 100644 --- a/dkg_feldmanvss.go +++ b/dkg_feldmanvss.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/dkg_feldmanvssq.go b/dkg_feldmanvssq.go index 61e81049..bc1bf72a 100644 --- a/dkg_feldmanvssq.go +++ b/dkg_feldmanvssq.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/dkg_jointfeldman.go b/dkg_jointfeldman.go index 3f82f5b6..14e05c6c 100644 --- a/dkg_jointfeldman.go +++ b/dkg_jointfeldman.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/dkg_test.go b/dkg_test.go index e225d5b9..7d28c546 100644 --- a/dkg_test.go +++ b/dkg_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/ecdsa.go b/ecdsa.go index 6a2ae135..0d3af994 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -21,168 +21,72 @@ package crypto // Elliptic Curve Digital Signature Algorithm is implemented as // defined in FIPS 186-4 (although the hash functions implemented in this package are SHA2 and SHA3). -// Most of the implementation is Go based and is not optimized for performance. - -// This implementation does not include any security against side-channel attacks. +// This implementation is not resistant against side-channel attacks or fault attacks. import ( - "crypto/ecdh" - "crypto/ecdsa" - "crypto/elliptic" + "bytes" "crypto/hkdf" - "crypto/rand" "crypto/sha256" "fmt" "math/big" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/onflow/crypto/hash" ) -const ( - // NIST P256 - SignatureLenECDSAP256 = 64 - PrKeyLenECDSAP256 = 32 - // PubKeyLenECDSAP256 is the size of uncompressed points on P256 - PubKeyLenECDSAP256 = 64 - - // SECG secp256k1 - SignatureLenECDSASecp256k1 = 64 - PrKeyLenECDSASecp256k1 = 32 - // PubKeyLenECDSASecp256k1 is the size of uncompressed points on secp256k1 - PubKeyLenECDSASecp256k1 = 64 -) - -// ecdsaAlgo embeds SignAlgo -type ecdsaAlgo struct { - // elliptic curve - curve elliptic.Curve - // the signing algo and parameters +// ecdsaContext embeds SigningAlgorithm +type ecdsaContext struct { + // the signing algo algo SigningAlgorithm + // curve prime field + curveP *big.Int + // curve order + curveN *big.Int + // curve order minus 1 divided by 2 (used for signature malleability analysis) + curveNdiv2 *big.Int } -// ECDSA contexts for each supported curve -// -// NIST P-256 curve -var p256Instance *ecdsaAlgo +const ecEncodingUncompressed = 0x4 -// SECG secp256k1 curve https://www.secg.org/sec2-v2.pdf -var secp256k1Instance *ecdsaAlgo +func initECDSA() { + // ECDSA with P256 + initECDSAP256() + // ECDSA with secp256k1 + initECDSASecp256k1() +} func bitsToBytes(bits int) int { return (bits + 7) >> 3 } -// signHash returns the signature of the input hash using the private key receiver. -// The signature is the concatenation bytes(r) || bytes(s), -// where `r` and `s` are padded to the curve order size. -// Current implementation of `sign` is randomized, mixing the entropy from the -// the system's crypto/rand, the private key and the hash. -// -// The caller must make sure that the hash is at least the curve order size. -func (sk *prKeyECDSA) signHash(h hash.Hash) (Signature, error) { - r, s, err := ecdsa.Sign(rand.Reader, sk.goPrKey, h) - if err != nil { - return nil, fmt.Errorf("ECDSA sign failed: %w", err) - } - rBytes := r.Bytes() - sBytes := s.Bytes() - nLen := bitsToBytes((sk.alg.curve.Params().N).BitLen()) - signature := make([]byte, 2*nLen) - // pad the signature with zeroes - copy(signature[nLen-len(rBytes):], rBytes) - copy(signature[2*nLen-len(sBytes):], sBytes) - return signature, nil -} - -// Sign signs an array of bytes -// -// The resulting signature is the concatenation bytes(r)||bytes(s), -// where r and s are padded to the curve order size. -// The private key is read only while sha2 and sha3 hashers are -// modified temporarily. -// -// The function returns: -// - (false, errNilHasher) if a hasher is nil -// - (false, invalidHasherSizeError) when the hasher's output size is less than the curve order (currently 32 bytes). -// - (nil, error) if an unexpected error occurs -// - (signature, nil) otherwise -func (sk *prKeyECDSA) Sign(data []byte, alg hash.Hasher) (Signature, error) { - if alg == nil { +func (a *ecdsaContext) checkAlgoAndComputeHash(msg []byte, hasher hash.Hasher) (hash.Hash, error) { + if hasher == nil { return nil, errNilHasher } - // check hasher's size is at least the curve order in bytes - nLen := bitsToBytes((sk.alg.curve.Params().N).BitLen()) - if alg.Size() < nLen { - return nil, invalidHasherSizeErrorf( - "hasher's size should be at least %d, got %d", nLen, alg.Size()) - } - - h := alg.ComputeHash(data) - return sk.signHash(h) -} - -// verifyHash implements ECDSA signature verification -func (pk *pubKeyECDSA) verifyHash(sig Signature, h hash.Hash) (bool, error) { - nLen := bitsToBytes((pk.alg.curve.Params().N).BitLen()) - - if len(sig) != 2*nLen { - return false, nil - } - - var r big.Int - var s big.Int - r.SetBytes(sig[:nLen]) - s.SetBytes(sig[nLen:]) - return ecdsa.Verify(pk.goPubKey, h, &r, &s), nil -} - -// Verify verifies a signature of an input data under the public key. -// -// If the input signature slice has an invalid length or fails to deserialize into valid -// scalars, the function returns false without an error. -// -// Public keys are read only, sha2 and sha3 hashers are -// modified temporarily. -// -// The function returns: -// - (false, errNilHasher) if a hasher is nil -// - (false, invalidHasherSizeError) when the hasher's output size is less than the curve order (currently 32 bytes). -// - (false, error) if an unexpected error occurs -// - (validity, nil) otherwise -func (pk *pubKeyECDSA) Verify(sig Signature, data []byte, alg hash.Hasher) (bool, error) { - if alg == nil { - return false, errNilHasher - } // check hasher's size is at least the curve order in bytes - nLen := bitsToBytes((pk.alg.curve.Params().N).BitLen()) - if alg.Size() < nLen { - return false, invalidHasherSizeErrorf( - "hasher's size should be at least %d, got %d", nLen, alg.Size()) + nLen := (a.curveN).BitLen() + if (hasher.Size() << 3) < nLen { + return nil, invalidHasherSizeErrorf( + "hasher's bit-size should be at least %d, got %d", nLen, hasher.Size()<<3) } - h := alg.ComputeHash(data) - return pk.verifyHash(sig, h) + h := hasher.ComputeHash(msg) + return h, nil } // signatureFormatCheck verifies the format of a serialized signature, // regardless of messages or public keys. // If FormatCheck returns false then the input is not a valid ECDSA // signature and will fail a verification against any message and public key. -func (a *ecdsaAlgo) signatureFormatCheck(sig Signature) bool { - N := a.curve.Params().N +func (a *ecdsaContext) signatureFormatCheck(sig Signature) bool { + N := a.curveN nLen := bitsToBytes(N.BitLen()) if len(sig) != 2*nLen { return false } - var r big.Int - var s big.Int - r.SetBytes(sig[:nLen]) - s.SetBytes(sig[nLen:]) + r, s := readTwoBigInts(sig, nLen) if r.Sign() == 0 || s.Sign() == 0 { return false @@ -198,62 +102,45 @@ func (a *ecdsaAlgo) signatureFormatCheck(sig Signature) bool { } var one = new(big.Int).SetInt64(1) +var two = new(big.Int).SetInt64(2) -// goecdsaMapKey maps the input seed to a private key -// of the Go crypto/ecdsa library. +// mapToPrivateKey simply maps the input seed to an ECDSA private key // The private scalar `d` satisfies 0 < d < n. -// Returned error is expected to be nil. -func goecdsaMapKey(curve elliptic.Curve, seed []byte) (*ecdsa.PrivateKey, error) { +// +// The function returns: +// - (nil, invalidInputsError) if the curve is not supported +// - (nil, error) if an unexpected error occurs +// - (sk, nil) if key mapping was successful +func (a *ecdsaContext) mapToPrivateKey(seed []byte) (PrivateKey, error) { d := new(big.Int).SetBytes(seed) - n := new(big.Int).Sub(curve.Params().N, one) - d.Mod(d, n) - d.Add(d, one) - return goecdsaPrivateKey(curve, d) // n > d > 0 at this point + NminusOne := new(big.Int).Sub(a.curveN, one) + d.Mod(d, NminusOne) + d.Add(d, one) // n > d > 0 at this point + return a.privateKey(d) } -// goecdsaPrivateKey creates a Go crypto/ecdsa private key using the -// input curve and scalar. -// Input scalar is assumed to be a non-zero integer modulo the curve order `n`. -// Error returns: -// - invalidInputsError if the input curve is unsupported -func goecdsaPrivateKey(curve elliptic.Curve, d *big.Int) (*ecdsa.PrivateKey, error) { - priv := new(ecdsa.PrivateKey) - priv.D = d - priv.PublicKey.Curve = curve - - // compute the crypto/ecdsa public key - if curve == elliptic.P256() { - // Perform the base scalar multiplication using crypto/ecdh, - // because crypto/elliptic deprecated `ScalarBaseMult`. - // - // We build the ecdh.PrivateKey directly from the scalar bytes - // instead of going through `priv.ECDH()`: since Go 1.26, - // ecdsa's `(*PrivateKey).ECDH` serializes the key via `(*PrivateKey).Bytes`, - // which reads the public affine coordinates `X`/`Y`. - // Those are not set yet at this point (we are computing them), - // so that path dereferences nil and panics. - // Constructing the ecdh key from the scalar avoids reading `X`/`Y` - // and works across Go versions. - scalarLen := bitsToBytes(curve.Params().N.BitLen()) - ecdhPriv, err := ecdh.P256().NewPrivateKey(d.FillBytes(make([]byte, scalarLen))) - if err != nil { - // at this point, no error is expected because the function can't be called - // with a zero scalar modulo `n` - return nil, fmt.Errorf("non expected error when creating an ECDH private key: %w", err) - } - // crypto/ecdh serialization uses SEC1 version 2 (https://www.secg.org/sec1-v2.pdf section 2.3.3). - // The bytes returned are `0x04 || X || Y` because the point is guaranteed to be non-infinity - ecdhPubBytes := ecdhPriv.PublicKey().Bytes() - pLen := bitsToBytes(curve.Params().P.BitLen()) - priv.PublicKey.X = new(big.Int).SetBytes(ecdhPubBytes[1 : 1+pLen]) - priv.PublicKey.Y = new(big.Int).SetBytes(ecdhPubBytes[1+pLen:]) - } else if curve == btcec.S256() { - // `ScalarBaseMult` is not deprecated in btcec's type `KoblitzCurve` - priv.PublicKey.X, priv.PublicKey.Y = btcec.S256().ScalarBaseMult(d.Bytes()) - } else { +// privateKey returns an ECDSA private key using the +// input scalar. +// +// Input scalar d is assumed to satisfy 0 < d < n before calling this function. +// +// The function returns: +// - (nil, invalidInputsError) if the curve is not supported +// - (nil, error) if an unexpected error occurs +// - (sk, nil) if key mapping was successful +func (a *ecdsaContext) privateKey(d *big.Int) (PrivateKey, error) { + dBytes := make([]byte, bitsToBytes(a.curveN.BitLen())) + d.FillBytes(dBytes) // dBytes is the big-endian encoding of d padded to the curve order + + // build the private key depending on the curve + switch a.algo { + case ECDSAP256: + return privateKeyECDSAP256(a, dBytes) + case ECDSASecp256k1: + return privateKeyECDSASecp256k1(a, dBytes), nil + default: return nil, invalidInputsErrorf("the curve is not supported") } - return priv, nil } // generatePrivateKey generates a private key for ECDSA @@ -261,7 +148,7 @@ func goecdsaPrivateKey(curve elliptic.Curve, d *big.Int) (*ecdsa.PrivateKey, err // // It is recommended to use a secure crypto RNG to generate the seed. // The seed must have enough entropy. -func (a *ecdsaAlgo) generatePrivateKey(seed []byte) (PrivateKey, error) { +func (a *ecdsaContext) generatePrivateKey(seed []byte) (PrivateKey, error) { if len(seed) < KeyGenSeedMinLen || len(seed) > KeyGenSeedMaxLen { return nil, invalidInputsErrorf("seed byte length should be between %d and %d", KeyGenSeedMinLen, KeyGenSeedMaxLen) @@ -274,7 +161,7 @@ func (a *ecdsaAlgo) generatePrivateKey(seed []byte) (PrivateKey, error) { salt := []byte("") // HKDF salt info := "" // HKDF info // use extra 128 bits to reduce the modular reduction bias - nLen := bitsToBytes((a.curve.Params().N).BitLen()) + nLen := bitsToBytes((a.curveN).BitLen()) okmLength := nLen + (securityBits / 8) // instantiate HKDF and extract okm @@ -284,23 +171,19 @@ func (a *ecdsaAlgo) generatePrivateKey(seed []byte) (PrivateKey, error) { } defer overwrite(okm) // overwrite okm - sk, err := goecdsaMapKey(a.curve, okm) + sk, err := a.mapToPrivateKey(okm) if err != nil { // no error is expected at this point return nil, fmt.Errorf("mapping the private key failed: %w", err) } - return &prKeyECDSA{ - alg: a, - goPrKey: sk, - pubKey: nil, // public key is not constructed - }, nil + return sk, nil } -func (a *ecdsaAlgo) rawDecodePrivateKey(der []byte) (PrivateKey, error) { - n := a.curve.Params().N +func (a *ecdsaContext) rawDecodePrivateKey(der []byte) (PrivateKey, error) { + n := a.curveN nLen := bitsToBytes(n.BitLen()) if len(der) != nLen { - return nil, invalidInputsErrorf("input has incorrect %s key size", a.algo) + return nil, invalidInputsErrorf("input has incorrect %s key size, should be %d", a.algo, nLen) } var d big.Int d.SetBytes(der) @@ -313,20 +196,16 @@ func (a *ecdsaAlgo) rawDecodePrivateKey(der []byte) (PrivateKey, error) { return nil, invalidInputsErrorf("zero private keys are not a valid %s key", a.algo) } - priv, err := goecdsaPrivateKey(a.curve, &d) // n > d > 0 at this point + sk, err := a.privateKey(&d) // n > d > 0 at this point if err != nil { // error is not expected at this point return nil, fmt.Errorf("building the private key failed: %w", err) } - return &prKeyECDSA{ - alg: a, - goPrKey: priv, - pubKey: nil, // public key is not constructed - }, nil + return sk, nil } -func (a *ecdsaAlgo) decodePrivateKey(der []byte) (PrivateKey, error) { +func (a *ecdsaContext) decodePrivateKey(der []byte) (PrivateKey, error) { return a.rawDecodePrivateKey(der) } @@ -335,236 +214,160 @@ func (a *ecdsaAlgo) decodePrivateKey(der []byte) (PrivateKey, error) { // Note that infinity point serialization isn't defined in this package so the input (or output) can never represent an infinity point. // Error Returns: // - invalidInputsError if the input is not a valid serialization of a public key on the given curve. -func (a *ecdsaAlgo) rawDecodePublicKey(der []byte) (PublicKey, error) { - curve := a.curve - p := (curve.Params().P) - pLen := bitsToBytes(p.BitLen()) - if len(der) != 2*pLen { - return nil, invalidInputsErrorf("input has incorrect %s key size, got %d, expects %d", - a.algo, len(der), 2*pLen) - } - var x, y big.Int - x.SetBytes(der[:pLen]) - y.SetBytes(der[pLen:]) - - // check the coordinates are valid field elements - if x.Cmp(p) >= 0 || y.Cmp(p) >= 0 { - return nil, invalidInputsErrorf("at least one coordinate is larger than the field prime for %s", a.algo) - } - - // all the curves supported for now have a cofactor equal to 1, - // so that checking the point is on curve is enough. - if curve == elliptic.P256() { - // use crypto/ecdh implementation to perform on curve check - // because crypto/elliptic deprecated `IsOnCurve`. - // ECDH's `NewPublicKey` checks the public key is on curve to avoid falling in small-order groups. - - // crypto/ecdh deserialization uses SEC1 version 2 (https://www.secg.org/sec1-v2.pdf section 2.3.3) - // except for infinity point. - // The bytes serialization for non-zero points is `0x04 || X || Y` - ecdhPubBytes := append([]byte{0x4}, der...) - - _, err := ecdh.P256().NewPublicKey(ecdhPubBytes) - if err != nil { - return nil, invalidInputsErrorf("input is not a point on curve P-256: %w", err) - } - } else if curve == btcec.S256() { - // `IsOnCurve` is not deprecated in btcec's type `KoblitzCurve` - if !btcec.S256().IsOnCurve(&x, &y) { - return nil, invalidInputsErrorf("input is not a point on curve secp256k1") - } - } else { +func (a *ecdsaContext) rawDecodePublicKey(input []byte) (PublicKey, error) { + switch a.algo { + case ECDSAP256: + return publicKeyECDSAP256(input) + case ECDSASecp256k1: + return publicKeyECDSASecp256k1(a, input) + default: return nil, invalidInputsErrorf("curve is not supported") } - - pk := ecdsa.PublicKey{ - Curve: a.curve, - X: &x, - Y: &y, - } - - return &pubKeyECDSA{a, &pk}, nil } -func (a *ecdsaAlgo) decodePublicKey(der []byte) (PublicKey, error) { +func (a *ecdsaContext) decodePublicKey(der []byte) (PublicKey, error) { return a.rawDecodePublicKey(der) } // decodePublicKeyCompressed returns a non-infinity public key given the bytes of a compressed // public key according to X9.62 section 4.3.6. -// The compressed representation uses an extra byte to disambiguate sign. // Note that infinity point serialization isn't defined in this package so the input (or output) // can never represent an infinity point. // Error Returns: // - invalidInputsError if the curve isn't supported or the input isn't a valid key serialization // on the given curve. -func (a *ecdsaAlgo) decodePublicKeyCompressed(pkBytes []byte) (PublicKey, error) { - expectedLen := bitsToBytes(a.curve.Params().BitSize) + 1 - if len(pkBytes) != expectedLen { - return nil, invalidInputsErrorf("input length incompatible, expected %d, got %d", expectedLen, len(pkBytes)) - } - var goPubKey *ecdsa.PublicKey - - if a.curve == elliptic.P256() { - x, y := elliptic.UnmarshalCompressed(a.curve, pkBytes) - if x == nil { - return nil, invalidInputsErrorf("input %x isn't a compressed serialization of a %v key", pkBytes, a.algo.String()) - } - goPubKey = new(ecdsa.PublicKey) - goPubKey.Curve = a.curve - goPubKey.X = x - goPubKey.Y = y - - } else if a.curve == btcec.S256() { - // use `btcec` because elliptic's `UnmarshalCompressed` doesn't work for SEC Koblitz curves - pk, err := btcec.ParsePubKey(pkBytes) - if err != nil { - return nil, invalidInputsErrorf("input %x isn't a compressed serialization of a %v key", pkBytes, a.algo.String()) - } - // convert to a crypto/ecdsa key - goPubKey = pk.ToECDSA() - } else { +func (a *ecdsaContext) decodePublicKeyCompressed(pkBytes []byte) (PublicKey, error) { + switch a.algo { + case ECDSAP256: + return p256DecodePublicKeyCompressed(pkBytes) + case ECDSASecp256k1: + return secp256k1DecodePublicKeyCompressed(pkBytes) + default: return nil, invalidInputsErrorf("the input curve is not supported") } - return &pubKeyECDSA{a, goPubKey}, nil -} - -// prKeyECDSA is the private key of ECDSA, it implements the interface PrivateKey -type prKeyECDSA struct { - // the signature algo - alg *ecdsaAlgo - // ecdsa private key - goPrKey *ecdsa.PrivateKey - // public key - pubKey *pubKeyECDSA } -var _ PrivateKey = (*prKeyECDSA)(nil) - // Algorithm returns the algo related to the private key -func (sk *prKeyECDSA) Algorithm() SigningAlgorithm { - return sk.alg.algo +func (a *ecdsaContext) Algorithm() SigningAlgorithm { + return a.algo } -// Size returns the length of the private key in bytes -func (sk *prKeyECDSA) Size() int { - return bitsToBytes((sk.alg.curve.Params().N).BitLen()) +type prKeyCommonECDSA struct { + // ECDSA context + *ecdsaContext } -// PublicKey returns the public key associated to the private key -func (sk *prKeyECDSA) PublicKey() PublicKey { - // construct the public key once - if sk.pubKey == nil { - sk.pubKey = &pubKeyECDSA{ - alg: sk.alg, - goPubKey: &sk.goPrKey.PublicKey, - } - } - return sk.pubKey +// Size returns the length of the private key in bytes +func (sk *prKeyCommonECDSA) Size() int { + return bitsToBytes((sk.curveN).BitLen()) } -// given a private key (d), returns a raw encoding bytes(d) in big endian -// padded to the private key length -func (sk *prKeyECDSA) rawEncode() []byte { - skBytes := sk.goPrKey.D.Bytes() - nLen := bitsToBytes((sk.alg.curve.Params().N).BitLen()) - skEncoded := make([]byte, nLen) - // pad sk with zeroes - copy(skEncoded[nLen-len(skBytes):], skBytes) - return skEncoded +// prKeyCommonECDSAString returns the string representation of an ECDSA private key. +// It is used by all ECDSA private keys regardless of the curve. +func prKeyCommonECDSAString(sk PrivateKey) string { + return fmt.Sprintf("%#x", sk.Encode()) } -// Encode returns a byte representation of a private key. -// a simple raw byte encoding in big endian is used for all curves -func (sk *prKeyECDSA) Encode() []byte { - return sk.rawEncode() +// pubKeyCommonECDSAString returns the string representation of an ECDSA public key. +// It is used by all ECDSA public keys regardless of the curve. +func pubKeyCommonECDSAString(pk PublicKey) string { + return fmt.Sprintf("%#x", pk.Encode()) } // Equals test the equality of two private keys -func (sk *prKeyECDSA) Equals(other PrivateKey) bool { - // check the key type - otherECDSA, ok := other.(*prKeyECDSA) - if !ok { +func prKeyCommonECDSAEquals(sk, other PrivateKey) bool { + // check the algorithm + if sk.Algorithm() != other.Algorithm() { return false } - // check the curve - if sk.alg.curve != otherECDSA.alg.curve { - return false - } - return sk.goPrKey.D.Cmp(otherECDSA.goPrKey.D) == 0 -} - -// String returns the hex string representation of the key. -func (sk *prKeyECDSA) String() string { - return fmt.Sprintf("%#x", sk.Encode()) + // check the scalar + return bytes.Equal(sk.Encode(), other.Encode()) } -// pubKeyECDSA is the public key of ECDSA, it implements PublicKey -type pubKeyECDSA struct { - // the signature algo - alg *ecdsaAlgo - // public key data - goPubKey *ecdsa.PublicKey +type pubKeyCommonECDSA struct { + // ECDSA context + *ecdsaContext } -var _ PublicKey = (*pubKeyECDSA)(nil) - -// Algorithm returns the the algo related to the private key -func (pk *pubKeyECDSA) Algorithm() SigningAlgorithm { - return pk.alg.algo +// Size returns the length of the public key in bytes +func (pk *pubKeyCommonECDSA) Size() int { + return 2 * bitsToBytes(pk.curveP.BitLen()) } -// Size returns the length of the public key in bytes -func (pk *pubKeyECDSA) Size() int { - return 2 * bitsToBytes((pk.goPubKey.Params().P).BitLen()) +// Equals test the equality of two private keys +func pubKeyCommonECDSAEquals(pk, other PublicKey) bool { + // check the algorithm + if pk.Algorithm() != other.Algorithm() { + return false + } + // check the point + return bytes.Equal(pk.Encode(), other.Encode()) } -// EncodeCompressed returns a compressed encoding according to X9.62 section 4.3.6. -// This compressed representation uses an extra byte to disambiguate parity. -// The expected input is a public key (x,y). -// -// Receiver point is guaranteed to be on curve and to be non-infinity because -// the package does not allow constructing infinity points or points not on curve. -func (pk *pubKeyECDSA) EncodeCompressed() []byte { - return elliptic.MarshalCompressed(pk.goPubKey.Curve, pk.goPubKey.X, pk.goPubKey.Y) +// Helper function to pad two big integers to "size" bytes and concatenate them. +// This helper is needed in serializations in ECDSA implementation. +// It assumes the output buffer has at least 2*size byte-length +func padToSizeAndConcat(output []byte, a, b *big.Int, size int) { + a.FillBytes(output[:size]) + b.FillBytes(output[size:]) } -// `rawEncode` returns a raw uncompressed encoding `bytes(x) || bytes(y)` given a public key (x,y). -// x and y are padded to the field size. -func (pk *pubKeyECDSA) rawEncode() []byte { - xBytes := pk.goPubKey.X.Bytes() - yBytes := pk.goPubKey.Y.Bytes() - Plen := bitsToBytes((pk.alg.curve.Params().P).BitLen()) - pkEncoded := make([]byte, 2*Plen) - // pad the public key coordinates with zeroes - copy(pkEncoded[Plen-len(xBytes):], xBytes) - copy(pkEncoded[2*Plen-len(yBytes):], yBytes) - return pkEncoded +// Helper function to read two big integers of "size" bytes each from a concatenate input buffer. +// This helper is needed when deserializing. +// It assumes the input buffer has at least 2*size byte-length. +func readTwoBigInts(input []byte, size int) (*big.Int, *big.Int) { + a := new(big.Int).SetBytes(input[:size]) + b := new(big.Int).SetBytes(input[size : 2*size]) + return a, b } -// Encode returns a byte representation of a public key. -// a simple uncompressed raw encoding X||Y is used for all curves -// X and Y are the big endian byte encoding of the x and y coordinates of the public key -func (pk *pubKeyECDSA) Encode() []byte { - return pk.rawEncode() +// isLowS returns true if the signature's S is in the lower range (S <= (n-1)/2) +func (a *ecdsaContext) isLowS(s *big.Int) bool { + return a.curveNdiv2.Cmp(s) >= 0 } -// Equals test the equality of two private keys -func (pk *pubKeyECDSA) Equals(other PublicKey) bool { - // check the key type - otherECDSA, ok := other.(*pubKeyECDSA) - if !ok { - return false +// signatureNormalizeS returns a signature with S normalized to low S. +// (same slice is returned if S is already normalized) +// It assumes len(sig) == 2*nLen where nLen is the byte-length of the curve order. +// This is needed when the underlying signature verification requires S to be in the lower range (to avoid signature malleability). In this package, verification allows high S signatures to be accepted. +// The function checks that S is in the correct range [0, n-1] before normalizing it. +// If S is not in the correct range, the function returns a false boolean. +// (S will be checked against 0 in the verification function - check against N is inlcuded here) +// returns: +// - newSig, true if S is in the valid range and was normalized to low S +// - nil, false if S was not in the correct range +func (a *ecdsaContext) signatureNormalizeS(sig []byte) ([]byte, bool) { + // read S + nLen := bitsToBytes(a.curveN.BitLen()) + s := new(big.Int).SetBytes(sig[nLen:]) // S >= 0 + if a.isLowS(s) { // S <= (n-1)/2 + return sig, true // S is in the valid range and no need to flip it } - // check the curve - if pk.alg.curve != otherECDSA.alg.curve { - return false + + if a.curveN.Cmp(s) <= 0 { // S >= n, invalid signature + return nil, false } - return (pk.goPubKey.X.Cmp(otherECDSA.goPubKey.X) == 0) && - (pk.goPubKey.Y.Cmp(otherECDSA.goPubKey.Y) == 0) + + // In the remaining case, (n-1)/2 < S < n and it is safe to flip + // i.e n-s is guaranteed to be in the range [1, (n-1)/2] + sComplement := new(big.Int).Sub(a.curveN, s) // n-S + // write it into a new signature + newSig := make([]byte, len(sig)) + copy(newSig, sig[:nLen]) // copy R + sComplement.FillBytes(newSig[nLen:]) // write S complement + return newSig, true } -// String returns the hex string representation of the key. -func (pk *pubKeyECDSA) String() string { - return fmt.Sprintf("%#x", pk.Encode()) +// Test function only to flip S in a signature. It is used for testing signature malleability +func (a *ecdsaContext) signatureFlipS(sig []byte) []byte { + // read S + nLen := bitsToBytes(a.curveN.BitLen()) + s := new(big.Int).SetBytes(sig[nLen:]) + // compute N-S + sComplement := new(big.Int).Sub(a.curveN, s) + // write it into a new signature + newSig := make([]byte, len(sig)) + copy(newSig, sig[:nLen]) // copy R + sComplement.FillBytes(newSig[nLen:]) // write S complement + return newSig } diff --git a/ecdsa_p256.go b/ecdsa_p256.go new file mode 100644 index 00000000..d2838985 --- /dev/null +++ b/ecdsa_p256.go @@ -0,0 +1,282 @@ +/* + * Flow Crypto + * + * Copyright Flow Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package crypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + "math/big" + + "github.com/onflow/crypto/hash" +) + +// ECDSA implementation on NIST-P256 is based on https://pkg.go.dev/crypto and https://pkg.go.dev/crypto/elliptic +// This implementation is not resistant against side-channel attacks or fault attacks. + +const ( + nLenP256 = 32 + pLenP256 = 32 +) + +const ( + // NIST P256 + SignatureLenECDSAP256 = 2 * nLenP256 + PrKeyLenECDSAP256 = nLenP256 + // PubKeyLenECDSAP256 is the size of uncompressed points on P256 + PubKeyLenECDSAP256 = 2 * pLenP256 +) + +// context of ECDSA on NIST P-256 +var p256Instance *ecdsaContext + +func initECDSAP256() { + curve := elliptic.P256() + n := curve.Params().N + nMinus1 := new(big.Int).Sub(n, one) + + p256Instance = &(ecdsaContext{ + curveP: curve.Params().P, + curveN: n, + curveNdiv2: new(big.Int).Div(nMinus1, two), // (N-1)/2 + algo: ECDSAP256, + }) +} + +// prKeyECDSAP256 is the private key of ECDSA on P256, it implements the interface PrivateKey +type prKeyECDSAP256 struct { + // ECDSA generic private key + *prKeyCommonECDSA + // go ecdsa standard lib private key + goPrKey *ecdsa.PrivateKey + // public key + pubKey *pubKeyECDSAP256 +} + +var _ PrivateKey = (*prKeyECDSAP256)(nil) + +// pubKeyECDSAP256 is the public key of ECDSA on P256, it implements PublicKey +type pubKeyECDSAP256 struct { + // ECDSA generic public key + *pubKeyCommonECDSA + // go ecdsa standard lib public key + goPubKey *ecdsa.PublicKey +} + +var _ PublicKey = (*pubKeyECDSAP256)(nil) + +// Input scalar d is assumed to satisfy 0 < d < n before calling this function. +func privateKeyECDSAP256(a *ecdsaContext, dBytes []byte) (*prKeyECDSAP256, error) { + internalSK, err := ecdsa.ParseRawPrivateKey(elliptic.P256(), dBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse raw private key: %w", err) + } + sk := &prKeyECDSAP256{ + prKeyCommonECDSA: &prKeyCommonECDSA{a}, + goPrKey: internalSK, + pubKey: nil, // public key is not constructed yet + } + return sk, nil +} + +// Sign signs an array of bytes +// +// The resulting signature is the concatenation bytes(r)||bytes(s), +// where r and s are padded to the curve order size. +// The private key is read only while sha2 and sha3 hashers are +// modified temporarily. +// +// The function returns: +// - (false, errNilHasher) if a hasher is nil +// - (false, invalidHasherSizeError) when the hasher's output size is less than the curve order (currently 32 bytes). +// - (nil, error) if an unexpected error occurs +// - (signature, nil) otherwise +func (sk *prKeyECDSAP256) Sign(msg []byte, hasher hash.Hasher) (Signature, error) { + hash, err := sk.checkAlgoAndComputeHash(msg, hasher) + if err != nil { + return nil, err + } + r, s, err := ecdsa.Sign(rand.Reader, sk.goPrKey, hash) + if err != nil { + return nil, fmt.Errorf("ECDSA sign failed: %w", err) + } + + signature := make([]byte, 2*nLenP256) + padToSizeAndConcat(signature, r, s, nLenP256) + return signature, nil +} + +// String returns the hex string representation of the private key +func (sk *prKeyECDSAP256) String() string { + return prKeyCommonECDSAString(sk) +} + +// returns a publicKeyECDSAP256 from (bytes(x) || bytes(y)) bytes +func publicKeyECDSAP256(XYBytes []byte) (*pubKeyECDSAP256, error) { + // deserialization uses SEC1 version 2 (https://www.secg.org/sec1-v2.pdf section 2.3.3) + // and includes on curve check. + // The bytes serialization for non-infinity points is `0x04 || X || Y` and infinity point should be rejected anyway + parsingBytes := append([]byte{ecEncodingUncompressed}, XYBytes...) + + // ParseUncompressedPublicKey includes x

= 0 || y.Cmp(a.curveP) >= 0 { + return nil, invalidInputsErrorf("at least one coordinate is larger than the field prime for %s", a.algo) + } + + // `IsOnCurve` includes checks for x

= p + ECDSASecp256k1, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F0000000000000000000000000000000000000000000000000000000000000000", + onflowCryptoErr, + }, { + ECDSAP256, "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000", + goCryptoErr, + }, { + // y >= p + ECDSASecp256k1, "0000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + onflowCryptoErr, + }, { + ECDSAP256, "0000000000000000000000000000000000000000000000000000000000000000FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", + goCryptoErr, + }, } - invalidPK2s := map[SigningAlgorithm]string{ - ECDSASecp256k1: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F0000000000000000000000000000000000000000000000000000000000000000", - ECDSAP256: "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000", + + for _, invalidPK := range invalidPKs { + pkBytes, err := hex.DecodeString(invalidPK.pk) + require.NoError(t, err) + pk, err := DecodePublicKey(invalidPK.signin, pkBytes) + require.Error(t, err) + assert.True(t, IsInvalidInputsError(err)) + assert.ErrorContains(t, err, invalidPK.errorMsg) + assert.Nil(t, pk) } - // invalidpk1 with x >= p - invalidPk1, err := hex.DecodeString(invalidPK1s[curve]) - require.NoError(t, err) - _, err = DecodePublicKey(curve, invalidPk1) - assert.Error(t, err) - assert.ErrorContains(t, err, "at least one coordinate is larger than the field prime for") - // invalidpk2 with y >= p - invalidPk2, err := hex.DecodeString(invalidPK2s[curve]) - require.NoError(t, err) - _, err = DecodePublicKey(curve, invalidPk2) - assert.Error(t, err) - assert.ErrorContains(t, err, "at least one coordinate is larger than the field prime for") }) } } @@ -270,52 +281,7 @@ func TestECDSAPublicKeyComputation(t *testing.T) { } } -// TestGoECDSAP256PrivateKeyConstruction exercises `goecdsaPrivateKey` for P-256 directly. -// -// This is a regression test for a panic that surfaced on Go 1.26: -// when constructing the key, the public affine coordinates `X`/`Y` are still nil -// (we are in the middle of computing them via base scalar multiplication). -// Since Go 1.26, `(*ecdsa.PrivateKey).ECDH` serializes the key through -// `(*PrivateKey).Bytes`, which reads `X`/`Y` and therefore panicked on the nil deref. -// The fix builds the ecdh key from the scalar bytes instead. -// -// The test asserts the construction does not panic and that the computed public key -// matches a known test vector, so it guards both the crash and the correctness of the -// scalar-based code path. -func TestGoECDSAP256PrivateKeyConstruction(t *testing.T) { - // scalar / expected public key pair, identical to the P-256 vector in - // TestECDSAPublicKeyComputation - const skHex = "6e37a39c31a05181bf77919ace790efd0bdbcaf42b5a52871fc112fceb918c95" - const xHex = "78a80dfe190a6068be8ddf05644c32d2540402ffc682442f6a9eeb96125d8681" - const yHex = "3789f92cf4afabf719aaba79ecec54b27e33a188f83158f6dd15ecb231b49808" - - skBytes, err := hex.DecodeString(skHex) - require.NoError(t, err) - d := new(big.Int).SetBytes(skBytes) - - // the call panicked on Go 1.26 before the fix - require.NotPanics(t, func() { - priv, err := goecdsaPrivateKey(elliptic.P256(), d) - require.NoError(t, err) - require.NotNil(t, priv) - - // the scalar must be preserved - assert.Equal(t, 0, priv.D.Cmp(d)) - - // the computed public affine coordinates must match the known vector - expectedX, ok := new(big.Int).SetString(xHex, 16) - require.True(t, ok) - expectedY, ok := new(big.Int).SetString(yHex, 16) - require.True(t, ok) - assert.Equal(t, 0, priv.PublicKey.X.Cmp(expectedX)) - assert.Equal(t, 0, priv.PublicKey.Y.Cmp(expectedY)) - - // the computed point must be on the curve - assert.True(t, elliptic.P256().IsOnCurve(priv.PublicKey.X, priv.PublicKey.Y)) - }) -} - -func TestSignatureFormatCheck(t *testing.T) { +func TestECDSASignatureFormatCheck(t *testing.T) { for _, curve := range ecdsaCurves { t.Run("valid signature", func(t *testing.T) { @@ -395,33 +361,6 @@ func TestSignatureFormatCheck(t *testing.T) { } } -func TestEllipticUnmarshalSecp256k1(t *testing.T) { - testVectors := []string{ - "028b10bf56476bf7da39a3286e29df389177a2fa0fca2d73348ff78887515d8da1", // IsOnCurve for elliptic returns false - "03d39427f07f680d202fe8504306eb29041aceaf4b628c2c69b0ec248155443166", // odd, IsOnCurve for elliptic returns false - "0267d1942a6cbe4daec242ea7e01c6cdb82dadb6e7077092deb55c845bf851433e", // arith of sqrt in elliptic doesn't match secp256k1 - "0345d45eda6d087918b041453a96303b78c478dce89a4ae9b3c933a018888c5e06", // odd, arith of sqrt in elliptic doesn't match secp256k1 - } - - for _, testVector := range testVectors { - // get the compressed bytes - publicBytes, err := hex.DecodeString(testVector) - require.NoError(t, err) - - // decompress, check that those are perfectly valid Secp256k1 public keys - retrieved, err := DecodePublicKeyCompressed(ECDSASecp256k1, publicBytes) - require.NoError(t, err) - - // check the compression is canonical by re-compressing to the same bytes - require.Equal(t, retrieved.EncodeCompressed(), publicBytes) - - // check that elliptic fails at decompressing them - x, y := elliptic.UnmarshalCompressed(btcec.S256(), publicBytes) - require.Nil(t, x) - require.Nil(t, y) - } -} - func BenchmarkECDSADecode(b *testing.B) { // random message seed := make([]byte, 50) @@ -483,3 +422,92 @@ func TestECDSAKeyGenerationBreakingChange(t *testing.T) { assert.Equal(t, test.expectedSK, sk.String()) } } + +// TestECDSAHighAndLowS checks that both signature malleability forms are accepted. +// +// For a valid signature (r,s), the pair (r,n-s) is also a valid signature of the same +// message under the same key. The package signature verification accepts both forms and should keep doing so. +// Rejecting the high-s form would be a breaking change for the applications using this package. +func TestECDSAHighAndLowS(t *testing.T) { + + var ecdsaContexts = map[SigningAlgorithm]*ecdsaContext{ + ECDSAP256: p256Instance, + ECDSASecp256k1: secp256k1Instance, + } + + t.Run("lowS and HighS pass", func(t *testing.T) { + for _, curve := range ecdsaCurves { + t.Run(curve.String(), func(t *testing.T) { + // generate a key and sign a random message + seed := make([]byte, KeyGenSeedMinLen) + _, err := crand.Read(seed) + require.NoError(t, err) + sk, err := GeneratePrivateKey(curve, seed) + require.NoError(t, err) + + msg := make([]byte, 10) + _, err = crand.Read(msg) + require.NoError(t, err) + + halg := hash.NewSHA3_256() + sig, err := sk.Sign(msg, halg) + require.NoError(t, err) + + // extract S and test the first case of S (can be low or high S) + _, s := readTwoBigInts(sig, ecdsaSigLen[curve]/2) + isLowS := ecdsaContexts[curve].isLowS(s) + + t.Run(fmt.Sprintf("low S equals %v", isLowS), func(t *testing.T) { + // the format check must accept both forms + wellFormed, err := SignatureFormatCheck(curve, sig) + require.NoError(t, err) + assert.True(t, wellFormed) + + // verification must accept the first form (can be low or high S) + valid, err := sk.PublicKey().Verify(sig, msg, halg) + require.NoError(t, err) + assert.True(t, valid) + }) + + // flip S to N-S to check the other case (can be low or high S) + t.Run(fmt.Sprintf("low S equals %v", !isLowS), func(t *testing.T) { + newSig := ecdsaContexts[curve].signatureFlipS(sig) + + // sanity check + _, newS := readTwoBigInts(newSig, ecdsaSigLen[curve]/2) + newIsLowS := ecdsaContexts[curve].isLowS(newS) + require.Equal(t, !newIsLowS, isLowS, "S didn't flip") // this test is correct because S cannot equal N-S since N is odd + + // the format check must accept both forms + wellFormed, err := SignatureFormatCheck(curve, newSig) + require.NoError(t, err) + assert.True(t, wellFormed) + + // verification must accept the second form (can be low or high S) + valid, err := sk.PublicKey().Verify(newSig, msg, halg) + require.NoError(t, err) + assert.True(t, valid) + }) + }) + } + }) + + // signatureNormalizeS must reject values S >= N + t.Run("check signatureNormalizeS", func(t *testing.T) { + for _, curve := range ecdsaCurves { + t.Run(curve.String(), func(t *testing.T) { + nLen := ecdsaSigLen[curve] / 2 + badSig := make([]byte, ecdsaSigLen[curve]) + // set all S bytes to 0xFF which makes S larger than N. + // R value does not matter in the function + for i := nLen; i < len(badSig); i++ { + badSig[i] = 0xFF + } + + newSig, validS := ecdsaContexts[curve].signatureNormalizeS(badSig) + assert.False(t, validS) + assert.Nil(t, newSig) + }) + } + }) +} diff --git a/go.mod b/go.mod index 344580d9..fb560979 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,18 @@ module github.com/onflow/crypto go 1.26.0 require ( - github.com/btcsuite/btcd/btcec/v2 v2.3.4 + github.com/ethereum/go-ethereum v1.16.8 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.36.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/crypto v0.54.0 gonum.org/v1/gonum v0.16.0 pgregory.net/rapid v0.4.7 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/kr/pretty v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.31.0 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + golang.org/x/sys v0.47.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 16ac77ab..29594720 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,35 @@ -github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= -github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/ethereum/go-ethereum v1.16.8 h1:LLLfkZWijhR5m6yrAXbdlTeXoqontH+Ga2f9igY7law= +github.com/ethereum/go-ethereum v1.16.8/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hash/hash_test.go b/hash/hash_test.go index 09de08d8..d1b49fff 100644 --- a/hash/hash_test.go +++ b/hash/hash_test.go @@ -21,13 +21,14 @@ package hash import ( "crypto/rand" "crypto/sha256" + "crypto/sha3" "crypto/sha512" "encoding/hex" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/crypto/sha3" + xsha3 "golang.org/x/crypto/sha3" ) // Sanity check of SHA3_256 @@ -260,7 +261,7 @@ func TestKeccak(t *testing.T) { value := make([]byte, i) _, err := rand.Read(value) require.NoError(t, err) - k := sha3.NewLegacyKeccak256() + k := xsha3.NewLegacyKeccak256() k.Write(value) expected := k.Sum(nil) diff --git a/internal/blst/non_cgo.go b/internal/blst/non_cgo.go deleted file mode 100644 index 324387c6..00000000 --- a/internal/blst/non_cgo.go +++ /dev/null @@ -1,3 +0,0 @@ -//go:build !cgo && no_cgo - -package blst diff --git a/no_cgo.go b/no_cgo.go deleted file mode 100644 index fd95f65c..00000000 --- a/no_cgo.go +++ /dev/null @@ -1,194 +0,0 @@ -//go:build !cgo && no_cgo - -package crypto - -// This file enables the build of the library when cgo is disabled, i.e when the environment -// variable `CGO_ENABLED` is set to `0`. -// The build without cgo succeeds but disables all algorithms working with -// the BLS12-381 curve (BLS signature, BLS threshold signature, BLS-based DKG and BLS-SPoCK). -// Any call to any of these algorithms would panic. - -import ( - "fmt" - - "github.com/onflow/crypto/hash" -) - -const ( - SignatureLenBLSBLS12381 = 0 - PubKeyLenBLSBLS12381 = 0 - PrKeyLenBLSBLS12381 = 0 -) - -func initBLS12381() {} - -func withFeature(feature string) string { - return fmt.Sprintf("%s is only supported with cgo, rebuild with CGO_ENABLED=1\n", feature) -} - -type blsBLS12381Algo struct { - algo SigningAlgorithm -} - -// BLS context on the BLS 12-381 curve -var blsInstance *blsBLS12381Algo - -func (a *blsBLS12381Algo) generatePrivateKey(ikm []byte) (PrivateKey, error) { - panic(withFeature("BLS signature")) -} - -func (a *blsBLS12381Algo) decodePrivateKey(privateKeyBytes []byte) (PrivateKey, error) { - panic(withFeature("BLS signature")) -} - -func (a *blsBLS12381Algo) decodePublicKey(publicKeyBytes []byte) (PublicKey, error) { - panic(withFeature("BLS signature")) -} - -func (a *blsBLS12381Algo) decodePublicKeyCompressed(publicKeyBytes []byte) (PublicKey, error) { - panic(withFeature("BLS signature")) -} - -func NewExpandMsgXOFKMAC128(domainTag string) hash.Hasher { - panic(withFeature("BLS hasher")) -} - -func IsBLSSignatureIdentity(s Signature) bool { - panic(withFeature("BLS signature")) -} - -func BLSInvalidSignature() Signature { - panic(withFeature("BLS signature")) -} - -func isG2Compressed() bool { - panic(withFeature("BLS12-381 curve")) -} - -func NewBLSThresholdSignatureParticipant( - groupPublicKey PublicKey, - sharePublicKeys []PublicKey, - threshold int, - myIndex int, - myPrivateKey PrivateKey, - message []byte, - dsTag string, -) (ThresholdSignatureParticipant, error) { - panic(withFeature("BLS threshold signature")) -} - -func NewBLSThresholdSignatureInspector( - groupPublicKey PublicKey, - sharePublicKeys []PublicKey, - threshold int, - message []byte, - dsTag string, -) (ThresholdSignatureInspector, error) { - panic(withFeature("BLS threshold signature")) -} - -func BLSReconstructThresholdSignature(size int, threshold int, - shares []Signature, signers []int) (Signature, error) { - _ = duplicatedSignerErrorf("") - _ = notEnoughSharesErrorf("") - panic(withFeature("BLS threshold signature")) -} - -func EnoughShares(threshold int, sharesNumber int) (bool, error) { - panic(withFeature("BLS threshold signature")) -} - -func BLSThresholdKeyGen(size int, threshold int, seed []byte) ([]PrivateKey, - []PublicKey, PublicKey, error) { - panic(withFeature("BLS threshold signature")) -} - -func NewFeldmanVSS(size int, threshold int, myIndex int, - processor DKGProcessor, dealerIndex int) (DKGState, error) { - _, _ = newDKGCommon(size, threshold, myIndex, - processor, dealerIndex) - panic(withFeature("BLS-DKG")) -} - -func NewFeldmanVSSQual(size int, threshold int, myIndex int, - processor DKGProcessor, dealerIndex int) (DKGState, error) { - _ = dkgFailureErrorf("") - _ = dkgInvalidStateTransitionErrorf("") - panic(withFeature("BLS-DKG")) -} - -func NewJointFeldman(size int, threshold int, myIndex int, - processor DKGProcessor) (DKGState, error) { - _ = feldmanVSSShare | feldmanVSSVerifVec | feldmanVSSComplaint | feldmanVSSComplaintAnswer - panic(withFeature("BLS-DKG")) -} - -func SPOCKProve(sk PrivateKey, data []byte, kmac hash.Hasher) (Signature, error) { - panic(withFeature("BLS-SPoCK")) -} - -func SPOCKVerifyAgainstData(pk PublicKey, proof Signature, data []byte, kmac hash.Hasher) (bool, error) { - panic(withFeature("BLS-SPoCK")) -} - -func SPOCKVerify(pk1 PublicKey, proof1 Signature, pk2 PublicKey, proof2 Signature) (bool, error) { - panic(withFeature("BLS-SPoCK")) -} - -func BLSGeneratePOP(sk PrivateKey) (Signature, error) { - panic(withFeature("BLS multi-sig")) -} - -func BLSVerifyPOP(pk PublicKey, s Signature) (bool, error) { - panic(withFeature("BLS multi-sig")) -} - -func AggregateBLSSignatures(sigs []Signature) (Signature, error) { - panic(withFeature("BLS multi-sig")) -} - -func AggregateBLSPrivateKeys(keys []PrivateKey) (PrivateKey, error) { - panic(withFeature("BLS multi-sig")) -} - -func AggregateBLSPublicKeys(keys []PublicKey) (PublicKey, error) { - panic(withFeature("BLS multi-sig")) -} - -func IdentityBLSPublicKey() PublicKey { - panic(withFeature("BLS multi-sig")) -} - -func RemoveBLSPublicKeys(aggKey PublicKey, keysToRemove []PublicKey) (PublicKey, error) { - panic(withFeature("BLS multi-sig")) -} - -func VerifyBLSSignatureOneMessage( - pks []PublicKey, s Signature, message []byte, kmac hash.Hasher, -) (bool, error) { - panic(withFeature("BLS multi-sig")) -} - -func VerifyBLSSignatureManyMessages( - pks []PublicKey, s Signature, messages [][]byte, kmac []hash.Hasher, -) (bool, error) { - panic(withFeature("BLS multi-sig")) -} - -func BatchVerifyBLSSignaturesOneMessage( - pks []PublicKey, sigs []Signature, message []byte, kmac hash.Hasher, -) ([]bool, error) { - panic(withFeature("BLS multi-sig")) -} - -func IsBLSAggregateEmptyListError(err error) bool { - panic(withFeature("BLS multi-sig")) -} - -func IsNotBLSKeyError(err error) bool { - panic(withFeature("BLS multi-sig")) -} - -func IsInvalidSignatureError(err error) bool { - panic(withFeature("BLS multi-sig")) -} diff --git a/no_cgo_test.go b/no_cgo_test.go deleted file mode 100644 index efc32a29..00000000 --- a/no_cgo_test.go +++ /dev/null @@ -1,46 +0,0 @@ -//go:build !cgo && no_cgo - -package crypto_test - -import ( - "testing" - - "github.com/onflow/crypto" - "github.com/stretchr/testify/assert" -) - -// Test all public functions requiring cgo. -// These functions must panic if built without cgo. -func TestNoRelicPanic(t *testing.T) { - assert.Panics(t, func() { _, _ = crypto.GeneratePrivateKey(crypto.BLSBLS12381, nil) }) - assert.Panics(t, func() { _, _ = crypto.DecodePrivateKey(crypto.BLSBLS12381, nil) }) - assert.Panics(t, func() { _, _ = crypto.DecodePublicKey(crypto.BLSBLS12381, nil) }) - assert.Panics(t, func() { _, _ = crypto.DecodePublicKeyCompressed(crypto.BLSBLS12381, nil) }) - assert.Panics(t, func() { _ = crypto.NewExpandMsgXOFKMAC128("") }) - assert.Panics(t, func() { _ = crypto.BLSInvalidSignature() }) - assert.Panics(t, func() { _, _ = crypto.BLSGeneratePOP(nil) }) - assert.Panics(t, func() { _, _ = crypto.BLSVerifyPOP(nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.AggregateBLSSignatures(nil) }) - assert.Panics(t, func() { _, _ = crypto.AggregateBLSPrivateKeys(nil) }) - assert.Panics(t, func() { _, _ = crypto.AggregateBLSPublicKeys(nil) }) - assert.Panics(t, func() { _ = crypto.IdentityBLSPublicKey() }) - assert.Panics(t, func() { _ = crypto.IsBLSAggregateEmptyListError(nil) }) - assert.Panics(t, func() { _ = crypto.IsInvalidSignatureError(nil) }) - assert.Panics(t, func() { _ = crypto.IsNotBLSKeyError(nil) }) - assert.Panics(t, func() { _ = crypto.IsBLSSignatureIdentity(nil) }) - assert.Panics(t, func() { _, _ = crypto.RemoveBLSPublicKeys(nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.VerifyBLSSignatureOneMessage(nil, nil, nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.VerifyBLSSignatureManyMessages(nil, nil, nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.BatchVerifyBLSSignaturesOneMessage(nil, nil, nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.SPOCKProve(nil, nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.SPOCKVerify(nil, nil, nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.SPOCKVerifyAgainstData(nil, nil, nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.NewBLSThresholdSignatureParticipant(nil, nil, 0, 0, nil, nil, "") }) - assert.Panics(t, func() { _, _ = crypto.NewBLSThresholdSignatureInspector(nil, nil, 0, nil, "") }) - assert.Panics(t, func() { _, _ = crypto.BLSReconstructThresholdSignature(0, 0, nil, nil) }) - assert.Panics(t, func() { _, _ = crypto.EnoughShares(0, 0) }) - assert.Panics(t, func() { _, _, _, _ = crypto.BLSThresholdKeyGen(0, 0, nil) }) - assert.Panics(t, func() { _, _ = crypto.NewFeldmanVSS(0, 0, 0, nil, 0) }) - assert.Panics(t, func() { _, _ = crypto.NewFeldmanVSSQual(0, 0, 0, nil, 0) }) - assert.Panics(t, func() { _, _ = crypto.NewJointFeldman(0, 0, 0, nil) }) -} diff --git a/sign.go b/sign.go index 9d5000fe..b6de3cbd 100644 --- a/sign.go +++ b/sign.go @@ -19,11 +19,8 @@ package crypto import ( - "crypto/elliptic" "fmt" - "github.com/btcsuite/btcd/btcec/v2" - "github.com/onflow/crypto/hash" ) @@ -48,7 +45,11 @@ const ( // String returns the string representation of this signing algorithm. func (f SigningAlgorithm) String() string { - return [...]string{"UNKNOWN", "BLS_BLS12381", "ECDSA_P256", "ECDSA_secp256k1"}[f] + names := [...]string{"UNKNOWN", "BLS_BLS12381", "ECDSA_P256", "ECDSA_secp256k1"} + if f < 0 || int(f) >= len(names) { + return "UNKNOWN" + } + return names[f] } // Signature is a generic type, regardless of the signature scheme @@ -83,20 +84,10 @@ func newSigner(algo SigningAlgorithm) (signer, error) { // Initialize the context of all algos func init() { // ECDSA - p256Instance = &(ecdsaAlgo{ - curve: elliptic.P256(), - algo: ECDSAP256, - }) - secp256k1Instance = &(ecdsaAlgo{ - curve: btcec.S256(), - algo: ECDSASecp256k1, - }) + initECDSA() // BLS initBLS12381() - blsInstance = &blsBLS12381Algo{ - algo: BLSBLS12381, - } } // SignatureFormatCheck verifies the format of a serialized signature, diff --git a/sign_test_utils.go b/sign_test_utils.go index b0f2a7ae..ebbd5150 100644 --- a/sign_test_utils.go +++ b/sign_test_utils.go @@ -243,6 +243,12 @@ func testEncodeDecode(t *testing.T, salg SigningAlgorithm) { assert.True(t, IsInvalidInputsError(err)) assert.Nil(t, sk) + bytes = make([]byte, skLens[salg]-1) + sk, err = DecodePrivateKey(salg, bytes) + require.Error(t, err) + assert.True(t, IsInvalidInputsError(err)) + assert.Nil(t, sk) + // public key pkLens := make(map[SigningAlgorithm]int) pkLens[ECDSAP256] = PubKeyLenECDSAP256 @@ -254,6 +260,12 @@ func testEncodeDecode(t *testing.T, salg SigningAlgorithm) { require.Error(t, err) assert.True(t, IsInvalidInputsError(err)) assert.Nil(t, pk) + + bytes = make([]byte, pkLens[salg]-1) + pk, err = DecodePublicKey(salg, bytes) + require.Error(t, err) + assert.True(t, IsInvalidInputsError(err)) + assert.Nil(t, pk) }) }) } diff --git a/spock.go b/spock.go index 96f1a593..5927c9be 100644 --- a/spock.go +++ b/spock.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto * diff --git a/spock_test.go b/spock_test.go index 1bf8d808..9efccf30 100644 --- a/spock_test.go +++ b/spock_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !no_cgo - /* * Flow Crypto *