Skip to content

Commit

Permalink
Marshal bn254 pubkeys
Browse files Browse the repository at this point in the history
  • Loading branch information
agouin committed Dec 18, 2023
1 parent 3d648f9 commit 0fd5d91
Show file tree
Hide file tree
Showing 13 changed files with 1,101 additions and 23 deletions.
8 changes: 4 additions & 4 deletions cmd/horcrux/cmd/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (

cometcrypto "github.com/cometbft/cometbft/crypto"
cometcryptoed25519 "github.com/cometbft/cometbft/crypto/ed25519"
cometcryptoencoding "github.com/cometbft/cometbft/crypto/encoding"
cometprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
"github.com/spf13/cobra"
"github.com/strangelove-ventures/horcrux/signer"
"github.com/strangelove-ventures/horcrux/signer/encoding"
"github.com/strangelove-ventures/horcrux/signer/proto"
amino "github.com/tendermint/go-amino"
"gopkg.in/yaml.v2"
)
Expand Down Expand Up @@ -98,7 +98,7 @@ func (key *v2CosignerKey) UnmarshalJSON(data []byte) error {
}

var pubkey cometcrypto.PubKey
var protoPubkey cometprotocrypto.PublicKey
var protoPubkey proto.PublicKey
err = protoPubkey.Unmarshal(aux.PubkeyBytes)

// Prior to the tendermint protobuf migration, the public key bytes in key files
Expand All @@ -118,7 +118,7 @@ func (key *v2CosignerKey) UnmarshalJSON(data []byte) error {
}
pubkey = pub
} else {
pubkey, err = cometcryptoencoding.PubKeyFromProto(protoPubkey)
pubkey, err = encoding.PubKeyFromProto(protoPubkey)
if err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions proto/buf.lock
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# Generated by buf. DO NOT EDIT.
version: v1
deps:
- remote: buf.build
owner: cosmos
repository: gogo-proto
commit: 5e5b9fdd01804356895f8f79a6f1ddc1
2 changes: 2 additions & 0 deletions proto/buf.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
version: v1
deps:
- buf.build/cosmos/gogo-proto
18 changes: 18 additions & 0 deletions proto/tendermint/crypto/keys.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
syntax = "proto3";
package tendermint.crypto;

option go_package = "github.com/strangelove-ventures/horcrux/signer/proto";

import "gogoproto/gogo.proto";

// PublicKey defines the keys available for use with Validators
message PublicKey {
option (gogoproto.compare) = true;
option (gogoproto.equal) = true;

oneof sum {
bytes ed25519 = 1;
bytes secp256k1 = 2;
bytes bn254 = 3;
}
}
174 changes: 174 additions & 0 deletions signer/bn254/bn254.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package bn254

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

"github.com/cometbft/cometbft/crypto"
"github.com/cometbft/cometbft/crypto/tmhash"
cometjson "github.com/cometbft/cometbft/libs/json"
ecdsa_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/ecdsa"
)

//-------------------------------------

var (
_ crypto.PrivKey = PrivKey{}
)

const (
PrivKeyName = "tendermint/PrivKeyBn254"
PubKeyName = "tendermint/PubKeyBn254"
// PubKeySize is is the size, in bytes, of public keys as used in this package.
PubKeySize = 32
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
PrivateKeySize = 64
// Size of an Bn254 signature. Namely the size of a compressed
// Bn254 point, and a field element. Both of which are 32 bytes.
SignatureSize = 64
// SeedSize is the size, in bytes, of private key seeds. These are the
// private key representations used by RFC 8032.
SeedSize = 32

KeyType = "bn254"
)

func init() {
cometjson.RegisterType(PubKey{}, PubKeyName)
cometjson.RegisterType(PrivKey{}, PrivKeyName)
}

// PrivKey implements crypto.PrivKey.
type PrivKey []byte

// Bytes returns the privkey byte format.
func (privKey PrivKey) Bytes() []byte {
return []byte(privKey)
}

// Sign produces a signature on the provided message.
// This assumes the privkey is wellformed in the golang format.
// The first 32 bytes should be random,
// corresponding to the normal bn254 private key.
// The latter 32 bytes should be the compressed public key.
// If these conditions aren't met, Sign will panic or produce an
// incorrect signature.
func (privKey PrivKey) Sign(msg []byte) ([]byte, error) {
priv := new(ecdsa_bn254.PrivateKey)
_, err := priv.SetBytes(privKey[:])
if err != nil {
return nil, err
}

hFunc := sha256.New()

return priv.Sign(msg, hFunc)
}

// PubKey gets the corresponding public key from the private key.
//
// Panics if the private key is not initialized.
func (privKey PrivKey) PubKey() crypto.PubKey {
// If the latter 32 bytes of the privkey are all zero, privkey is not
// initialized.
initialized := false
for _, v := range privKey[32:] {
if v != 0 {
initialized = true
break
}
}

if !initialized {
panic("Expected bn254 PrivKey to include concatenated pubkey bytes")
}

pubkeyBytes := make([]byte, PubKeySize)
copy(pubkeyBytes, privKey[32:])
return PubKey(pubkeyBytes)
}

// Equals - you probably don't need to use this.
// Runs in constant time based on length of the keys.
func (privKey PrivKey) Equals(other crypto.PrivKey) bool {
if otherEd, ok := other.(PrivKey); ok {
return subtle.ConstantTimeCompare(privKey[:], otherEd[:]) == 1
}

return false
}

func (privKey PrivKey) Type() string {
return KeyType
}

// GenPrivKey generates a new bn254 private key.
// It uses OS randomness in conjunction with the current global random seed
// in cometbft/libs/rand to generate the private key.
func GenPrivKey() PrivKey {
return genPrivKey(crypto.CReader())
}

// genPrivKey generates a new bn254 private key using the provided reader.
func genPrivKey(rand io.Reader) PrivKey {
priv, err := ecdsa_bn254.GenerateKey(rand)
if err != nil {
panic(err)
}

return PrivKey(priv.Bytes())
}

//-------------------------------------

var _ crypto.PubKey = PubKey{}

// PubKey implements crypto.PubKey for the Bn254 signature scheme.
type PubKey []byte

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

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

func (pubKey PubKey) VerifySignature(msg []byte, sig []byte) bool {
// make sure we use the same algorithm to sign
if len(sig) != SignatureSize {
return false
}

pub := new(ecdsa_bn254.PublicKey)
if _, err := pub.SetBytes(pubKey[:]); err != nil {
return false
}

valid, _ := pub.Verify(msg, sig, sha256.New())
return valid
}

func (pubKey PubKey) String() string {
return fmt.Sprintf("PubKeyBn254{%X}", []byte(pubKey))
}

func (pubKey PubKey) Type() string {
return KeyType
}

func (pubKey PubKey) Equals(other crypto.PubKey) bool {
if otherEd, ok := other.(PubKey); ok {
return bytes.Equal(pubKey[:], otherEd[:])
}

return false
}
37 changes: 24 additions & 13 deletions signer/cosigner_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package signer

import (
"encoding/json"
"errors"
"fmt"
"os"

cometcrypto "github.com/cometbft/cometbft/crypto"
cometcryptoed25519 "github.com/cometbft/cometbft/crypto/ed25519"
cometcryptoencoding "github.com/cometbft/cometbft/crypto/encoding"
cometprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
"github.com/strangelove-ventures/horcrux/signer/bn254"
"github.com/strangelove-ventures/horcrux/signer/encoding"
"github.com/strangelove-ventures/horcrux/signer/proto"
"github.com/tendermint/go-amino"
)

Expand All @@ -22,7 +25,17 @@ type CosignerKey struct {
func (key *CosignerKey) MarshalJSON() ([]byte, error) {
type Alias CosignerKey

protoPubkey, err := cometcryptoencoding.PubKeyToProto(cometcryptoed25519.PubKey(key.PubKey))
var pub cometcrypto.PubKey
switch key.KeyType {
case CosignerKeyTypeBn254:
pub = bn254.PubKey(key.PubKey)
case CosignerKeyTypeEd25519:
fallthrough
default:
pub = cometcryptoed25519.PubKey(key.PubKey)
}

protoPubkey, err := encoding.PubKeyToProto(pub)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -55,7 +68,7 @@ func (key *CosignerKey) UnmarshalJSON(data []byte) error {
}

var pubkey cometcrypto.PubKey
var protoPubkey cometprotocrypto.PublicKey
var protoPubkey proto.PublicKey
err := protoPubkey.Unmarshal(aux.PubkeyBytes)

// Prior to the tendermint protobuf migration, the public key bytes in key files
Expand All @@ -65,19 +78,17 @@ func (key *CosignerKey) UnmarshalJSON(data []byte) error {
// To support reading the public key bytes from these key files, we fallback to
// amino unmarshalling if the protobuf unmarshalling fails
if err != nil {
var pub cometcryptoed25519.PubKey
codec := amino.NewCodec()
codec.RegisterInterface((*cometcrypto.PubKey)(nil), nil)
codec.RegisterConcrete(cometcryptoed25519.PubKey{}, "tendermint/PubKeyEd25519", nil)
codec.RegisterConcrete(cometcryptoed25519.PubKey{}, "tendermint/PubKeyBn254", nil)

errInner := codec.UnmarshalBinaryBare(aux.PubkeyBytes, &pub)
if errInner != nil {
return err
var pub cometcryptoed25519.PubKey
if errInner := codec.UnmarshalBinaryBare(aux.PubkeyBytes, &pub); errInner != nil {
return fmt.Errorf("error in unmarshal ed25519: %w", errors.Join(err, errInner))
}
pubkey = pub
} else {
pubkey, err = cometcryptoencoding.PubKeyFromProto(protoPubkey)
pubkey, err = encoding.PubKeyFromProto(protoPubkey)
if err != nil {
return err
}
Expand All @@ -88,16 +99,16 @@ func (key *CosignerKey) UnmarshalJSON(data []byte) error {
}

// LoadCosignerKey loads a CosignerKey from file.
func LoadCosignerKey(file string) (CosignerKey, error) {
pvKey := CosignerKey{}
func LoadCosignerKey(file string) (*CosignerKey, error) {
pvKey := new(CosignerKey)
keyJSONBytes, err := os.ReadFile(file)
if err != nil {
return pvKey, err
}

err = json.Unmarshal(keyJSONBytes, &pvKey)
if err != nil {
return pvKey, err
return pvKey, fmt.Errorf("error in unmarshal: %w", err)
}

return pvKey, nil
Expand Down
Loading

0 comments on commit 0fd5d91

Please sign in to comment.