From eb8905b243e691ea35cc3f9f3edc90d121cdfe9e Mon Sep 17 00:00:00 2001 From: Freddie Coleman Date: Sat, 5 Mar 2022 10:25:49 +0000 Subject: [PATCH] implement Signer interface for bls12381 --- asserter/construction.go | 4 +- keys/errors.go | 3 + keys/keys.go | 19 ++++++ keys/signer_bls12381.go | 121 +++++++++++++++++++++++++++++++++++ keys/signer_bls12381_test.go | 113 ++++++++++++++++++++++++++++++++ types/curve_type.go | 1 + types/signature_type.go | 1 + 7 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 keys/signer_bls12381.go create mode 100644 keys/signer_bls12381_test.go diff --git a/asserter/construction.go b/asserter/construction.go index 9bbefbf3..449c64c9 100644 --- a/asserter/construction.go +++ b/asserter/construction.go @@ -215,7 +215,7 @@ func CurveType( curve types.CurveType, ) error { switch curve { - case types.Secp256k1, types.Secp256r1, types.Edwards25519, types.Tweedle, types.Pallas: + case types.Secp256k1, types.Secp256r1, types.Edwards25519, types.Tweedle, types.Pallas, types.Bls12381: return nil default: return fmt.Errorf("%w: %s", ErrCurveTypeNotSupported, curve) @@ -304,7 +304,7 @@ func SignatureType( signature types.SignatureType, ) error { switch signature { - case types.Ecdsa, types.EcdsaRecovery, types.Ed25519, types.Schnorr1, types.SchnorrPoseidon: + case types.Ecdsa, types.EcdsaRecovery, types.Ed25519, types.Schnorr1, types.SchnorrPoseidon, types.BlsG2Element: return nil default: return fmt.Errorf("%w: %s", ErrSignatureTypeNotSupported, signature) diff --git a/keys/errors.go b/keys/errors.go index fa4678bb..0fff3b9f 100644 --- a/keys/errors.go +++ b/keys/errors.go @@ -39,6 +39,9 @@ var ( ErrKeyGenPallasFailed = errors.New( "keygen: error generating key pair for pallas curve type", ) + ErrKeyGenBls12381Failed = errors.New( + "keygen: error generating key pair for bls12381 curve type", + ) ErrCurveTypeNotSupported = errors.New("not a supported CurveType") ErrSignUnsupportedPayloadSignatureType = errors.New( diff --git a/keys/keys.go b/keys/keys.go index 22a15641..1b143cd3 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -24,6 +24,7 @@ import ( "math/big" "github.com/btcsuite/btcd/btcec" + "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" "github.com/coinbase/kryptology/pkg/signatures/schnorr/mina" "github.com/coinbase/rosetta-sdk-go/asserter" @@ -210,6 +211,22 @@ func GenerateKeypair(curve types.CurveType) (*KeyPair, error) { CurveType: curve, } + keyPair = &KeyPair{ + PublicKey: pubKey, + PrivateKey: rawPrivKeyBytes, + } + case types.Bls12381: + rawPubKey, rawPrivKey, err := bls_sig.NewSigBasic().Keygen() + rawPubKeyBytes, _ := rawPubKey.MarshalBinary() + rawPrivKeyBytes, _ := rawPrivKey.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrKeyGenBls12381Failed, err) + } + pubKey := &types.PublicKey{ + Bytes: rawPubKeyBytes, + CurveType: curve, + } + keyPair = &KeyPair{ PublicKey: pubKey, PrivateKey: rawPrivKeyBytes, @@ -252,6 +269,8 @@ func (k *KeyPair) Signer() (Signer, error) { return &SignerSecp256r1{k}, nil case types.Pallas: return &SignerPallas{k}, nil + case types.Bls12381: + return &SignerBls12381{k}, nil default: return nil, fmt.Errorf("%w: %s", ErrCurveTypeNotSupported, k.PublicKey.CurveType) } diff --git a/keys/signer_bls12381.go b/keys/signer_bls12381.go new file mode 100644 index 00000000..a74ea4d9 --- /dev/null +++ b/keys/signer_bls12381.go @@ -0,0 +1,121 @@ +// Copyright 2022 Coinbase, Inc. +// +// 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 keys + +import ( + "errors" + "fmt" + + "github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig" + "github.com/coinbase/rosetta-sdk-go/asserter" + "github.com/coinbase/rosetta-sdk-go/types" +) + +var ErrBlsTransactionValidationErr = errors.New("transaction with bls validation failed") + +type SignerBls12381 struct { + KeyPair *KeyPair +} + +func (s *SignerBls12381) PublicKey() *types.PublicKey { + return s.KeyPair.PublicKey +} + +// Sign transaction payloads using a KeyPair +func (s *SignerBls12381) Sign( + payload *types.SigningPayload, + sigType types.SignatureType, +) (*types.Signature, error) { + err := s.KeyPair.IsValid() + if err != nil { + return nil, err + } + + if !(payload.SignatureType == types.BlsG2Element || payload.SignatureType == "") { + return nil, fmt.Errorf( + "%w: expected %v but got %v", + ErrSignUnsupportedPayloadSignatureType, + types.BlsG2Element, + payload.SignatureType, + ) + } + + if sigType != types.BlsG2Element { + return nil, fmt.Errorf( + "%w: expected %v but got %v", + ErrSignUnsupportedSignatureType, + types.BlsG2Element, + sigType, + ) + } + + // Generate private key bytes + privKeyBytes := s.KeyPair.PrivateKey + privKey := &bls_sig.SecretKey{} + _ = privKey.UnmarshalBinary(privKeyBytes) + + bls := bls_sig.NewSigBasic() + sig, err := bls.Sign(privKey, payload.Bytes) + if err != nil { + return nil, err + } + sigBytes, _ := sig.MarshalBinary() + + return &types.Signature{ + SigningPayload: payload, + PublicKey: s.KeyPair.PublicKey, + SignatureType: payload.SignatureType, + Bytes: sigBytes, + }, nil +} + +// Verify verifies a Signature, by checking the validity of a Signature, +// the SigningPayload, and the PublicKey of the Signature. +func (s *SignerBls12381) Verify(signature *types.Signature) error { + if signature.SignatureType != types.BlsG2Element { + return fmt.Errorf( + "%w: expected %v but got %v", + ErrVerifyUnsupportedPayloadSignatureType, + types.BlsG2Element, + signature.SignatureType, + ) + } + + pubKeyBytes := signature.PublicKey.Bytes + pubKey := &bls_sig.PublicKey{} + _ = pubKey.UnmarshalBinary(pubKeyBytes) + + sigBytes := signature.Bytes + sig := &bls_sig.Signature{} + _ = sig.UnmarshalBinary(sigBytes) + + err := asserter.Signatures([]*types.Signature{signature}) + if err != nil { + return fmt.Errorf("%w: %s", ErrVerifyFailed, err) + } + + bls := bls_sig.NewSigBasic() + result, err := bls.Verify(pubKey, signature.SigningPayload.Bytes, sig) + + if err != nil { + return err + } + + if !result { + return fmt.Errorf("%w: %s", ErrVerifyFailed, "Verify failed") + } + + return nil +} diff --git a/keys/signer_bls12381_test.go b/keys/signer_bls12381_test.go new file mode 100644 index 00000000..eb0dd565 --- /dev/null +++ b/keys/signer_bls12381_test.go @@ -0,0 +1,113 @@ +// Copyright 2022 Coinbase, Inc. +// +// 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 keys + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/coinbase/rosetta-sdk-go/types" +) + +var signerBls12381 Signer + +func init() { + bls12381Keypair, _ := GenerateKeypair(types.Bls12381) + signerBls12381, _ = bls12381Keypair.Signer() + + unsignedTxStr := "a7ca4bce10200d073ef10c46e9d27c3b4e31263d4c07fbec447650fcc1b286" + + "300e8ecf25c0560f9cb5aa673247fb6a6f95fb73164d1bf5d41288f57ea40517d9da86c44a289ed" + + "2b6f9d3bf9a750ee96dbf905073f8ae56c80100ef47f5585acf70baff8c72c3b8a833181fb3edf4" + + "328fccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb" + txnBytes = []byte(unsignedTxStr) +} + +func TestSignBls12381(t *testing.T) { + type payloadTest struct { + payload *types.SigningPayload + err bool + errMsg error + } + + var payloadTests = []payloadTest{ + {mockPayload(txnBytes, types.BlsG2Element), false, nil}, + {mockPayload(txnBytes, ""), false, nil}, + {mockPayload(txnBytes, types.Ecdsa), true, ErrSignUnsupportedPayloadSignatureType}, + { + mockPayload(txnBytes, types.EcdsaRecovery), + true, + ErrSignUnsupportedPayloadSignatureType, + }, + } + for _, test := range payloadTests { + signature, err := signerBls12381.Sign(test.payload, types.BlsG2Element) + + if !test.err { + assert.NoError(t, err) + assert.Len(t, signature.Bytes, 96) + assert.Equal(t, signerBls12381.PublicKey(), signature.PublicKey) + } else { + assert.Contains(t, err.Error(), test.errMsg.Error()) + } + } +} + +func TestVerifyBls(t *testing.T) { + type signatureTest struct { + signature *types.Signature + errMsg error + } + + payload := mockPayload(txnBytes, types.BlsG2Element) + testSignature, err := signerBls12381.Sign(payload, types.BlsG2Element) + assert.NoError(t, err) + + simpleBytes := make([]byte, 32) + copy(simpleBytes, "hello") + + var signatureTests = []signatureTest{ + {mockSignature( + types.Ecdsa, + signerBls12381.PublicKey(), + txnBytes, + simpleBytes), ErrVerifyUnsupportedPayloadSignatureType}, + {mockSignature( + types.EcdsaRecovery, + signerBls12381.PublicKey(), + txnBytes, + simpleBytes), ErrVerifyUnsupportedPayloadSignatureType}, + {mockSignature( + types.BlsG2Element, + signerBls12381.PublicKey(), + simpleBytes, + testSignature.Bytes), ErrVerifyFailed}, + } + + for _, test := range signatureTests { + err := signerBls12381.Verify(test.signature) + assert.Contains(t, err.Error(), test.errMsg.Error()) + } + + // happy path + goodSignature := mockSignature( + types.BlsG2Element, + signerBls12381.PublicKey(), + txnBytes, + testSignature.Bytes, + ) + + assert.Equal(t, nil, signerBls12381.Verify(goodSignature)) +} diff --git a/types/curve_type.go b/types/curve_type.go index 4100c9c0..0239f411 100644 --- a/types/curve_type.go +++ b/types/curve_type.go @@ -27,6 +27,7 @@ type CurveType string // List of CurveType const ( + Bls12381 CurveType = "bls12381" Secp256k1 CurveType = "secp256k1" Secp256r1 CurveType = "secp256r1" Edwards25519 CurveType = "edwards25519" diff --git a/types/signature_type.go b/types/signature_type.go index 42cfb1f2..e2c10f3b 100644 --- a/types/signature_type.go +++ b/types/signature_type.go @@ -29,6 +29,7 @@ type SignatureType string // List of SignatureType const ( + BlsG2Element SignatureType = "bls12381_g2_element" Ecdsa SignatureType = "ecdsa" EcdsaRecovery SignatureType = "ecdsa_recovery" Ed25519 SignatureType = "ed25519"