Skip to content

Commit

Permalink
Bls (#104)
Browse files Browse the repository at this point in the history
* bls signature for basic account

* benchmark for bls and ed25519

* added bls sig verify cost to genesis

* Revert "Merge branch 'fetchai:master' into bls"

This reverts commit a5dd8ea, reversing
changes made to 082e071.

* format using go tools

* nuisance golangci-lint errors
  • Loading branch information
kitounliu authored and daeMOn63 committed Nov 12, 2021
1 parent 5467a1e commit ae40b72
Show file tree
Hide file tree
Showing 21 changed files with 1,048 additions and 62 deletions.
5 changes: 5 additions & 0 deletions crypto/codec/amino.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package codec

import (
"github.com/cosmos/cosmos-sdk/crypto/keys/bls12381"
"github.com/tendermint/tendermint/crypto/sr25519"

"github.com/cosmos/cosmos-sdk/codec"
Expand All @@ -22,6 +23,8 @@ func RegisterCrypto(cdc *codec.LegacyAmino) {
secp256k1.PubKeyName, nil)
cdc.RegisterConcrete(&kmultisig.LegacyAminoPubKey{},
kmultisig.PubKeyAminoRoute, nil)
cdc.RegisterConcrete(&bls12381.PubKey{},
bls12381.PubKeyName, nil)

cdc.RegisterInterface((*cryptotypes.PrivKey)(nil), nil)
cdc.RegisterConcrete(sr25519.PrivKey{},
Expand All @@ -30,4 +33,6 @@ func RegisterCrypto(cdc *codec.LegacyAmino) {
ed25519.PrivKeyName, nil)
cdc.RegisterConcrete(&secp256k1.PrivKey{},
secp256k1.PrivKeyName, nil)
cdc.RegisterConcrete(&bls12381.PrivKey{},
bls12381.PrivKeyName, nil)
}
2 changes: 2 additions & 0 deletions crypto/codec/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package codec

import (
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/bls12381"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
Expand All @@ -14,4 +15,5 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &ed25519.PubKey{})
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &secp256k1.PubKey{})
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &multisig.LegacyAminoPubKey{})
registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &bls12381.PubKey{})
}
42 changes: 42 additions & 0 deletions crypto/hd/algo.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hd

import (
"github.com/cosmos/cosmos-sdk/crypto/keys/bls12381"
bip39 "github.com/cosmos/go-bip39"

"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
Expand All @@ -20,11 +21,15 @@ const (
Ed25519Type = PubKeyType("ed25519")
// Sr25519Type represents the Sr25519Type signature system.
Sr25519Type = PubKeyType("sr25519")
// Bls12381Type represents the Bls12381Type signature system.
Bls12381Type = PubKeyType("bls12381")
)

var (
// Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters.
Secp256k1 = secp256k1Algo{}
// Bls12381 uses blst implememtation of bls signatures
Bls12381 = bls12381Algo{}
)

type DeriveFn func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error)
Expand Down Expand Up @@ -69,3 +74,40 @@ func (s secp256k1Algo) Generate() GenerateFn {
return &secp256k1.PrivKey{Key: bzArr}
}
}

type bls12381Algo struct {
}

func (s bls12381Algo) Name() PubKeyType {
return Bls12381Type
}

// todo: replace bitcoin private key generation
// Derive derives and returns the bls12381 private key for the given seed and HD path.
func (s bls12381Algo) Derive() DeriveFn {
return func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) {
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
if err != nil {
return nil, err
}

masterPriv, ch := ComputeMastersFromSeed(seed)
if len(hdPath) == 0 {
return masterPriv[:], nil
}
derivedKey, err := DerivePrivateKeyForPath(masterPriv, ch, hdPath)

return derivedKey, err
}
}

// Generate generates a bls12381 private key from the given bytes.
func (s bls12381Algo) Generate() GenerateFn {
return func(bz []byte) types.PrivKey {
var bzArr = make([]byte, bls12381.SeedSize)
copy(bzArr, bz)
sk := bls12381.GenPrivKeyFromSecret(bzArr)

return sk
}
}
2 changes: 1 addition & 1 deletion crypto/keyring/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ type keystore struct {
func newKeystore(kr keyring.Keyring, opts ...Option) keystore {
// Default options for keybase
options := Options{
SupportedAlgos: SigningAlgoList{hd.Secp256k1},
SupportedAlgos: SigningAlgoList{hd.Secp256k1, hd.Bls12381},
SupportedAlgosLedger: SigningAlgoList{hd.Secp256k1},
}

Expand Down
225 changes: 225 additions & 0 deletions crypto/keys/bls12381/bls12381.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package bls12381

import (
"crypto/subtle"
"fmt"
"io"

"github.com/cosmos/cosmos-sdk/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/types/errors"
blst "github.com/supranational/blst/bindings/go"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/tmhash"
)

const (
PrivKeyName = "tendermint/PrivKeyBls12381"
PubKeyName = "tendermint/PubKeyBls12381"
// PubKeySize is is the size, in bytes, of public keys as used in this package.
PubKeySize = 96
// PrivKeySize is the size, in bytes, of private keys as used in this package.
// Uncompressed public key
PrivKeySize = 32
// SignatureSize is the size of a bls signature. Namely the size of a compressed
// G2 point.
SignatureSize = 96
keyType = "bls12381"
SeedSize = 32
)

var _ cryptotypes.PrivKey = &PrivKey{}
var _ codec.AminoMarshaler = &PrivKey{}

// Bytes returns the byte representation of the Private Key.
func (privKey *PrivKey) Bytes() []byte {
return privKey.Key
}

// PubKey performs the point-scalar multiplication from the privKey on the
// generator point to get the pubkey.
func (privKey *PrivKey) PubKey() cryptotypes.PubKey {
sk := new(blst.SecretKey).Deserialize(privKey.Key)
if sk == nil {
panic("Failed to deserialize secret key!")
}
pk := new(blst.P1Affine).From(sk)
pkBytes := pk.Serialize()

return &PubKey{Key: pkBytes}
}

// Sign produces a signature on the provided message.
// This assumes the privkey is wellformed in the golang format.

func (privKey *PrivKey) Sign(msg []byte) ([]byte, error) {
dst := []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_")
sk := new(blst.SecretKey).Deserialize(privKey.Key)
if sk == nil {
panic("Failed to deserialize secret key!")
}

sig := new(blst.P2Affine).Sign(sk, msg, dst)
if sig == nil {
panic("Failed to sign message!")
}

sigBytes := sig.Compress()

return sigBytes, nil
}

// Equals - you probably don't need to use this.
// Runs in constant time based on length of the
func (privKey *PrivKey) Equals(other cryptotypes.LedgerPrivKey) bool {
return privKey.Type() == other.Type() && subtle.ConstantTimeCompare(privKey.Bytes(), other.Bytes()) == 1
}

func (privKey *PrivKey) Type() string {
return keyType
}

// MarshalAmino overrides Amino binary marshalling.
func (privKey PrivKey) MarshalAmino() ([]byte, error) {
return privKey.Key, nil
}

// UnmarshalAmino overrides Amino binary marshalling.
func (privKey *PrivKey) UnmarshalAmino(bz []byte) error {
if len(bz) != PrivKeySize {
return fmt.Errorf("invalid privkey size")
}
privKey.Key = bz

return nil
}

// MarshalAminoJSON overrides Amino JSON marshalling.
func (privKey PrivKey) MarshalAminoJSON() ([]byte, error) {
// When we marshal to Amino JSON, we don't marshal the "key" field itself,
// just its contents (i.e. the key bytes).
return privKey.MarshalAmino()
}

// UnmarshalAminoJSON overrides Amino JSON marshalling.
func (privKey *PrivKey) UnmarshalAminoJSON(bz []byte) error {
return privKey.UnmarshalAmino(bz)
}

// GenPrivKey generates a new BLS private key on curve bls12-381 private key.
// It uses OS randomness to generate the private key.
func GenPrivKey() *PrivKey {
return &PrivKey{Key: genPrivKey(crypto.CReader())}
}

// genPrivKey generates a new secp256k1 private key using the provided reader.
func genPrivKey(rand io.Reader) []byte {
var ikm [SeedSize]byte
_, err := io.ReadFull(rand, ikm[:])
if err != nil {
panic(err)
}

sk := blst.KeyGen(ikm[:])
if sk == nil {
panic("Failed to generate secret key!")
}

skBytes := sk.Serialize()

return skBytes
}

// GenPrivKeyFromSecret hashes the secret with SHA2, and uses
// that 32 byte output to create the private key.
// NOTE: secret should be the output of a KDF like bcrypt,
// if it's derived from user input.
func GenPrivKeyFromSecret(secret []byte) *PrivKey {
ikm := crypto.Sha256(secret) // Not Ripemd160 because we want 32 bytes.
skBytes := blst.KeyGen(ikm).Serialize()

return &PrivKey{Key: skBytes}
}

var _ cryptotypes.PubKey = &PubKey{}
var _ codec.AminoMarshaler = &PubKey{}

// Validate public key, infinity and subgroup checking
func (pubKey *PubKey) Validate() bool {
pk := new(blst.P1Affine).Deserialize(pubKey.Key)
return pk.KeyValidate()
}

// Address is the SHA256-20 of the raw pubkey bytes.
func (pubKey *PubKey) Address() crypto.Address {
if len(pubKey.Key) != PubKeySize {
panic("pubkey is incorrect size")
}
return crypto.Address(tmhash.SumTruncated(pubKey.Key))
}

// Bytes returns the PubKey byte format.
func (pubKey *PubKey) Bytes() []byte {
return pubKey.Key
}

// VerifySignature assumes public key is already validated
func (pubKey *PubKey) VerifySignature(msg []byte, sig []byte) bool {
// make sure we use the same algorithm to sign
pk := new(blst.P1Affine).Deserialize(pubKey.Key)
if pk == nil {
panic("Failed to deserialize public key")
}

sigma := new(blst.P2Affine).Uncompress(sig)
if sigma == nil {
panic("Failed to deserialize signature")
}

dst := []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_")

return sigma.Verify(true, pk, false, msg, dst)
}

func (pubKey *PubKey) String() string {
return fmt.Sprintf("PubKeyBls12381{%X}", pubKey.Key)
}

func (pubKey *PubKey) Type() string {
return keyType
}

func (pubKey *PubKey) Equals(other cryptotypes.PubKey) bool {
if pubKey.Type() != other.Type() {
return false
}

return subtle.ConstantTimeCompare(pubKey.Bytes(), other.Bytes()) == 1
}

// MarshalAmino overrides Amino binary marshalling.
func (pubKey PubKey) MarshalAmino() ([]byte, error) {
return pubKey.Key, nil
}

// UnmarshalAmino overrides Amino binary marshalling.
func (pubKey *PubKey) UnmarshalAmino(bz []byte) error {
if len(bz) != PubKeySize {
return errors.Wrap(errors.ErrInvalidPubKey, "invalid pubkey size")
}
pubKey.Key = bz

return nil
}

// MarshalAminoJSON overrides Amino JSON marshalling.
func (pubKey PubKey) MarshalAminoJSON() ([]byte, error) {
// When we marshal to Amino JSON, we don't marshal the "key" field itself,
// just its contents (i.e. the key bytes).
return pubKey.MarshalAmino()
}

// UnmarshalAminoJSON overrides Amino JSON marshalling.
func (pubKey *PubKey) UnmarshalAminoJSON(bz []byte) error {
return pubKey.UnmarshalAmino(bz)
}
36 changes: 36 additions & 0 deletions crypto/keys/bls12381/bls12381_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package bls12381_test

import (
"github.com/cosmos/cosmos-sdk/crypto/keys/bls12381"
bench "github.com/cosmos/cosmos-sdk/crypto/keys/internal/benchmarking"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"testing"
)

func TestSignAndValidateBls12381(t *testing.T) {
privKey := bls12381.GenPrivKey()
pubKey := privKey.PubKey()

msg := crypto.CRandBytes(1000)
sig, err := privKey.Sign(msg)
require.Nil(t, err)

// Test the signature
assert.True(t, pubKey.VerifySignature(msg, sig))

}

func BenchmarkSignBls(b *testing.B) {
privKey := bls12381.GenPrivKey()

bench.BenchmarkSigning(b, privKey)

}

func BenchmarkVerifyBls(b *testing.B) {
privKey := bls12381.GenPrivKey()

bench.BenchmarkVerification(b, privKey)
}
Loading

0 comments on commit ae40b72

Please sign in to comment.