Skip to content

Commit

Permalink
Merge pull request #599 from onflow/cf/cherry-pick-crypto
Browse files Browse the repository at this point in the history
Cherry pick crypto changes
  • Loading branch information
chasefleming authored Mar 6, 2024
2 parents 8845642 + 998812a commit 3178c0d
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 43 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Ensure go bin path is in path (especially for CI)
PATH := $(PATH):$(GOPATH)/bin

# include script to possibly set a crypto flag for older machines
include crypto_adx_flag.mk

CGO_FLAG := CGO_CFLAGS=$(CRYPTO_FLAG)

.PHONY: test
test:
GO111MODULE=on go test -coverprofile=coverage.txt ./...
GO111MODULE=on $(CGO_FLAG) go test -coverprofile=coverage.txt ./...

.PHONY: coverage
coverage: test
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ To start using the SDK, install Go 1.13 or above and run go get:
go get github.com/onflow/flow-go-sdk
```

Building and running Go commands with the SDK require enabling cgo : `CGO_ENABLED=1`

## Generating Keys

Flow uses [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)
Expand Down
2 changes: 1 addition & 1 deletion crypto/awskms/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (s *Signer) Sign(message []byte) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("awskms: failed to sign: %w", err)
}
sig, err := internal.ParseSignature(result.Signature, s.curve)
sig, err := internal.ParseECDSASignature(result.Signature, s.curve)
if err != nil {
return nil, fmt.Errorf("awskms: failed to parse signature: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion crypto/cloudkms/cloudkms.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import (
"strings"

kms "cloud.google.com/go/kms/apiv1"
"cloud.google.com/go/kms/apiv1/kmspb"
"google.golang.org/api/option"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"

"github.com/onflow/flow-go-sdk/crypto"
)
Expand Down
12 changes: 6 additions & 6 deletions crypto/cloudkms/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@
package cloudkms

import (
kms "cloud.google.com/go/kms/apiv1"
"cloud.google.com/go/kms/apiv1/kmspb"
"context"
"fmt"
"hash/crc32"

kms "cloud.google.com/go/kms/apiv1"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go-sdk/crypto/internal"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
"google.golang.org/protobuf/types/known/wrapperspb"
"hash/crc32"
)

var _ crypto.Signer = (*Signer)(nil)
Expand Down Expand Up @@ -87,7 +86,8 @@ func (s *Signer) Sign(message []byte) ([]byte, error) {
DataCrc32C: checksum(message),
}
} else {
// this is guaranteed to only return supported hash algos by KMS
// this is guaranteed to only return supported hash algos by KMS,
// since `s.hashAlgo` has been checked when the signer object was created.
hasher, err := crypto.NewHasher(s.hashAlgo)
if err != nil {
return nil, fmt.Errorf("cloudkms: failed to sign: %w", err)
Expand All @@ -104,7 +104,7 @@ func (s *Signer) Sign(message []byte) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("cloudkms: failed to sign: %w", err)
}
sig, err := internal.ParseSignature(result.Signature, s.curve)
sig, err := internal.ParseECDSASignature(result.Signature, s.curve)
if err != nil {
return nil, fmt.Errorf("cloudkms: failed to parse signature: %w", err)
}
Expand Down
37 changes: 24 additions & 13 deletions crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"errors"
"fmt"

"github.com/onflow/flow-go/crypto"
"github.com/onflow/crypto"
)

// SignatureAlgorithm is an identifier for a signature algorithm (and parameters if applicable).
Expand All @@ -38,6 +38,8 @@ const (
ECDSA_P256 = crypto.ECDSAP256
// ECDSA_secp256k1 is ECDSA on secp256k1 curve
ECDSA_secp256k1 = crypto.ECDSASecp256k1
// BLS_BLS12_381 is BLS on BLS12-381 curve
BLS_BLS12_381 = crypto.BLSBLS12381
)

// StringToSignatureAlgorithm converts a string to a SignatureAlgorithm.
Expand All @@ -47,19 +49,30 @@ func StringToSignatureAlgorithm(s string) SignatureAlgorithm {
return ECDSA_P256
case ECDSA_secp256k1.String():
return ECDSA_secp256k1
case BLS_BLS12_381.String():
return BLS_BLS12_381
default:
return UnknownSignatureAlgorithm
}
}

// CompatibleAlgorithms returns true if the signature and hash algorithms is a valid pair for a signing key
// supported by the package.
// CompatibleAlgorithms returns true if the signature and hash algorithms can be a valid pair for generating
// or verifying a signature, supported by the package.
//
// The package currently supports ECDSA with the 2 curves P-256 and secp256k1. Both curves can be paired with
// a supported hash function of 256-bits output (SHA2-256, SHA3-256, Keccak256)
// If the function returns `false`, the inputs cannot be paired. If the function
// returns `true`, the inputs can be paired, under the condition that variable output size
// hashers (currently KMAC128) are set with a compatible output size.
//
// Signature generation and verification functions would check the hash output constraints.
func CompatibleAlgorithms(sigAlgo SignatureAlgorithm, hashAlgo HashAlgorithm) bool {
if sigAlgo == ECDSA_P256 || sigAlgo == ECDSA_secp256k1 {
if hashAlgo == SHA2_256 || hashAlgo == SHA3_256 || hashAlgo == Keccak256 {
if hashAlgo == SHA2_256 || hashAlgo == SHA3_256 ||
hashAlgo == Keccak256 || hashAlgo == KMAC128 {
return true
}
}
if sigAlgo == BLS_BLS12_381 {
if hashAlgo == KMAC128 {
return true
}
}
Expand Down Expand Up @@ -105,8 +118,10 @@ func NewInMemorySigner(privateKey PrivateKey, hashAlgo HashAlgorithm) (InMemoryS
privateKey.Algorithm(), hashAlgo)
}

// The error is ignored because the hash algorithm is valid at this point
hasher, _ := NewHasher(hashAlgo)
hasher, err := NewHasher(hashAlgo)
if err != nil {
return InMemorySigner{}, fmt.Errorf("signer with hasher %s can't be instantiated with this function", hashAlgo)
}

return InMemorySigner{
PrivateKey: privateKey,
Expand Down Expand Up @@ -135,13 +150,9 @@ func NewNaiveSigner(privateKey PrivateKey, hashAlgo HashAlgorithm) (NaiveSigner,
// The key generation process extracts and expands the entropy of the seed.
const MinSeedLength = crypto.KeyGenSeedMinLen

func keyGenerationKMACTag(sigAlgo SignatureAlgorithm) []byte {
return []byte(fmt.Sprintf("%s Key Generation", sigAlgo))
}

// GeneratePrivateKey generates a private key with the specified signature algorithm from the given seed.
// Note that the output key is directly mapped from the seed. The seed is therefore equivalent to the private key.
// This implementation is pure software and does not include any isolation or secure-hardware protecion.
// This implementation is pure software and does not include any isolation or secure-hardware protection.
// The function should not be used for sensitive keys (for instance production keys) unless extra protection measures
// are taken.
func GeneratePrivateKey(sigAlgo SignatureAlgorithm, seed []byte) (PrivateKey, error) {
Expand Down
19 changes: 15 additions & 4 deletions crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ import (
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go-sdk/crypto"
fgcrypto "github.com/onflow/flow-go/crypto"
)

func TestGeneratePrivateKey(t *testing.T) {
// key algorithms currently supported by the SDK
supportedAlgos := []crypto.SignatureAlgorithm{
crypto.ECDSA_P256,
crypto.ECDSA_secp256k1,
crypto.BLS_BLS12_381,
}

// key algorithms not currently supported by the SDK
unsupportedAlgos := []crypto.SignatureAlgorithm{
fgcrypto.BLSBLS12381,
crypto.UnknownSignatureAlgorithm,
}

// key algorithm that does not represent any valid algorithm
Expand Down Expand Up @@ -78,14 +78,14 @@ func TestGeneratePrivateKey(t *testing.T) {
t.Run("Seed length exactly equal", func(t *testing.T) {
sk, err := crypto.GeneratePrivateKey(sigAlgo, equalSeed)
require.NoError(t, err)
assert.NotEqual(t, nil, sk)
assert.NotNil(t, sk)
assert.Equal(t, sigAlgo, sk.Algorithm())
})

t.Run("Valid signature algorithm", func(t *testing.T) {
sk, err := crypto.GeneratePrivateKey(sigAlgo, longSeed)
require.NoError(t, err)
assert.NotEqual(t, nil, sk)
assert.NotNil(t, sk)
assert.Equal(t, sigAlgo, sk.Algorithm())
})

Expand Down Expand Up @@ -123,6 +123,17 @@ func TestGeneratePrivateKey(t *testing.T) {
})
}

// TestBLS_BLS_12_381 sanity-checks that it is possible to create a signer
// with BLS
func TestBLS_BLS_12_381(t *testing.T) {
sk, err := crypto.GeneratePrivateKey(crypto.BLS_BLS12_381, makeSeed(t, crypto.MinSeedLength))
require.NoError(t, err)
hasher := crypto.NewBLSHasher("random_tag")
signer := crypto.InMemorySigner{sk, hasher}
_, err = signer.Sign([]byte("random_message"))
require.Nil(t, err)
}

func makeSeed(t *testing.T, l int) []byte {
seed := make([]byte, l)
_, err := rand.Read(seed)
Expand Down
35 changes: 34 additions & 1 deletion crypto/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
package crypto

import (
"errors"
"fmt"

"github.com/onflow/flow-go/crypto/hash"
"github.com/onflow/crypto"
"github.com/onflow/crypto/hash"
)

type Hasher = hash.Hasher
Expand All @@ -37,6 +39,7 @@ const (
SHA3_256 = hash.SHA3_256
SHA3_384 = hash.SHA3_384
Keccak256 = hash.Keccak_256
KMAC128 = hash.KMAC128
)

// StringToHashAlgorithm converts a string to a HashAlgorithm.
Expand All @@ -52,6 +55,8 @@ func StringToHashAlgorithm(s string) HashAlgorithm {
return SHA3_384
case Keccak256.String():
return Keccak256
case KMAC128.String():
return KMAC128

default:
return UnknownHashAlgorithm
Expand All @@ -61,6 +66,7 @@ func StringToHashAlgorithm(s string) HashAlgorithm {
// NewHasher initializes and returns a new hasher with the given hash algorithm.
//
// This function returns an error if the hash algorithm is invalid.
// KMAC128 cannot be instantiated with this function. Use `NewKMAC_128` instead.
func NewHasher(algo HashAlgorithm) (Hasher, error) {
switch algo {
case SHA2_256:
Expand All @@ -73,6 +79,8 @@ func NewHasher(algo HashAlgorithm) (Hasher, error) {
return NewSHA3_384(), nil
case Keccak256:
return NewKeccak_256(), nil
case KMAC128:
return nil, errors.New("KMAC128 can't be instantiated with this function")
default:
return nil, fmt.Errorf("invalid hash algorithm %s", algo)
}
Expand Down Expand Up @@ -102,3 +110,28 @@ func NewSHA3_384() Hasher {
func NewKeccak_256() Hasher {
return hash.NewKeccak_256()
}

// NewKMAC_128 returns a new KMAC instance
// - `key` is the KMAC key (the key size is compared to the security level).
// - `customizer` is the customization string. It can be left empty if no customization
// is required.
//
// NewKeccak_256 returns a new instance of KMAC128
func NewKMAC_128(key []byte, customizer []byte, outputSize int) (Hasher, error) {
return hash.NewKMAC_128(key, customizer, outputSize)
}

// NewBLSHasher returns a hasher that can be used for BLS signing and verifying.
// It abstracts the complexities of meeting the right conditions of a BLS
// hasher.
//
// The hasher returned is the the expand-message step in the BLS hash-to-curve.
// It uses a xof (extendable output function) based on KMAC128. It therefore has
// a 128-bytes output.
// The `tag` parameter is a domain separation string.
//
// Check https://pkg.go.dev/github.com/onflow/crypto#NewExpandMsgXOFKMAC128 for
// more info on the hasher generation underneath.
func NewBLSHasher(tag string) hash.Hasher {
return crypto.NewExpandMsgXOFKMAC128(tag)
}
8 changes: 4 additions & 4 deletions crypto/internal/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import (
"github.com/onflow/flow-go-sdk/crypto"
)

// parseSignature parses an asn1 stucture (R,S) into a slice of bytes as required by the `Siger.Sign` method.
func ParseSignature(kmsSignature []byte, curve crypto.SignatureAlgorithm) ([]byte, error) {
// ParseECDSASignature parses an ECDSA asn1 structure (R,S) into a slice of bytes as required by the `Siger.Sign` method.
func ParseECDSASignature(kmsSignature []byte, curve crypto.SignatureAlgorithm) ([]byte, error) {
var parsedSig struct{ R, S *big.Int }
if _, err := asn1.Unmarshal(kmsSignature, &parsedSig); err != nil {
return nil, fmt.Errorf("asn1.Unmarshal: %w", err)
Expand All @@ -44,7 +44,7 @@ func ParseSignature(kmsSignature []byte, curve crypto.SignatureAlgorithm) ([]byt
return signature, nil
}

// returns the curve order size in bytes (used to padd R and S of the ECDSA signature)
// returns the curve order size in bytes (used to pad R and S of the ECDSA signature)
// Only P-256 and secp256k1 are supported. The calling function should make sure
// the function is only called with one of the 2 curves.
func curveOrder(curve crypto.SignatureAlgorithm) int {
Expand All @@ -54,6 +54,6 @@ func curveOrder(curve crypto.SignatureAlgorithm) int {
case crypto.ECDSA_secp256k1:
return 32
default:
panic("the curve is not supported")
panic("parameter is not supported")
}
}
25 changes: 25 additions & 0 deletions crypto_adx_flag.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This script can be imported by Makefiles in order to set the `CRYPTO_FLAG` automatically.
# The `CRYPTO_FLAG` is a Go command flag that should be used when the machine's CPU executing
# the command may not support ADX instructions.
# For new machines that support ADX instructions, the `CRYPTO_FLAG` flag is not needed (or set
# to an empty string).

# First detect ADX support:
# `ADX_SUPPORT` is 1 if ADX instructions are supported on the current machine and 0 otherwise.
ifeq ($(shell uname -s),Linux)
# detect ADX support on the CURRENT linux machine.
ADX_SUPPORT := $(shell if ([ -f "/proc/cpuinfo" ] && grep -q -e '^flags.*\badx\b' /proc/cpuinfo); then echo 1; else echo 0; fi)
else
# on non-linux machines, set the flag to 1 by default
ADX_SUPPORT := 1
endif

# Then, set `CRYPTO_FLAG`
# the crypto package uses BLST source files underneath which may use ADX instructions.
ifeq ($(ADX_SUPPORT), 1)
# if ADX instructions are supported on the current machine, default is to use a fast ADX implementation
CRYPTO_FLAG := ""
else
# if ADX instructions aren't supported, this CGO flags uses a slower non-ADX implementation
CRYPTO_FLAG := "-O -D__BLST_PORTABLE__"
endif
2 changes: 1 addition & 1 deletion event.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (
"time"

"github.com/onflow/cadence"
"github.com/onflow/crypto/hash"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go/crypto/hash"
)

// List of built-in account event types.
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ require (
github.com/aws/aws-sdk-go-v2/service/kms v1.20.1
github.com/ethereum/go-ethereum v1.9.13
github.com/onflow/cadence v0.42.9
github.com/onflow/flow-go/crypto v0.24.9
github.com/onflow/crypto v0.25.0
github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20221202093946-932d1c70e288
github.com/onflow/sdks v0.5.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.2
google.golang.org/api v0.114.0
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.30.0
)
Expand Down Expand Up @@ -70,7 +69,9 @@ require (
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
gonum.org/v1/gonum v0.13.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading

0 comments on commit 3178c0d

Please sign in to comment.