Skip to content

Commit

Permalink
docs: add spec for latest EIP712 signatures (#1234)
Browse files Browse the repository at this point in the history
Adds spec for latest EIP712 signature changes. Also refactors the code structure a bit.

category: docs
ticket: #1203
  • Loading branch information
dB2510 authored Oct 6, 2022
1 parent 54d0553 commit 772f768
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 123 deletions.
25 changes: 9 additions & 16 deletions cluster/cluster_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,18 @@ import (
)

func TestDefinitionVerify(t *testing.T) {
var err error

secret0, op0 := randomOperator(t)
secret1, op1 := randomOperator(t)

t.Run("verify definition v1.3", func(t *testing.T) {
definition := randomDefinition(t, op0, op1)

configHash, err := hashDefinition(definition, true)
require.NoError(t, err)

chainID, err := eth2util.ForkVersionToChainID(definition.ForkVersion)
definition.Operators[0], err = signOperator(secret0, definition, op0)
require.NoError(t, err)

definition.Operators[0], err = signOperator(secret0, op0, configHash, chainID)
require.NoError(t, err)

definition.Operators[1], err = signOperator(secret1, op1, configHash, chainID)
definition.Operators[1], err = signOperator(secret1, definition, op1)
require.NoError(t, err)

err = definition.VerifySignatures()
Expand Down Expand Up @@ -86,14 +82,8 @@ func TestDefinitionVerify(t *testing.T) {
definition := randomDefinition(t, op0, op1)
definition.Operators[0] = Operator{} // Operator with no address, enr sig or config sig

configHash, err := hashDefinition(definition, true)
require.NoError(t, err)

chainID, err := eth2util.ForkVersionToChainID(definition.ForkVersion)
require.NoError(t, err)

// Only operator 1 signed.
definition.Operators[1], err = signOperator(secret1, op1, configHash, chainID)
definition.Operators[1], err = signOperator(secret1, definition, op1)
require.NoError(t, err)

err = definition.VerifySignatures()
Expand Down Expand Up @@ -138,5 +128,8 @@ func randomDefinition(t *testing.T, op0, op1 Operator) Definition {
// TODO(xenowits): Remove the line below when v1.3 is the current version.
definition.Version = v1_3

return definition
resp, err := definition.SetDefinitionHashes()
require.NoError(t, err)

return resp
}
1 change: 1 addition & 0 deletions cluster/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func TestEncode(t *testing.T) {
// Definition version prior to v1.3.0 don't support EIP712 signatures.
if version == "v1.0.0" || version == "v1.1.0" || version == "v1.2.0" {
for i := range definition.Operators {
// Set to empty values instead of nil to align with unmarshalled json.
definition.Operators[i].ConfigSignature = []byte{}
definition.Operators[i].ENRSignature = []byte{}
}
Expand Down
15 changes: 2 additions & 13 deletions cluster/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (

"github.com/obolnetwork/charon/app/errors"
"github.com/obolnetwork/charon/app/z"
"github.com/obolnetwork/charon/eth2util"
"github.com/obolnetwork/charon/p2p"
)

Expand Down Expand Up @@ -152,16 +151,6 @@ func (d Definition) VerifySignatures() error {
return errors.New("older version signatures not supported")
}

configHash, err := hashDefinition(d, true)
if err != nil {
return errors.Wrap(err, "config hash")
}

chainID, err := eth2util.ForkVersionToChainID(d.ForkVersion)
if err != nil {
return errors.Wrap(err, "fork version to chain id")
}

var noSigs int
for _, o := range d.Operators {
// Completely unsigned operators are also fine, assuming a single cluster-wide operator.
Expand All @@ -179,7 +168,7 @@ func (d Definition) VerifySignatures() error {
}

// Check that we have a valid config signature for each operator.
digest, err := digestEIP712(eip712TypeConfigHash, to0xHex(configHash[:]), chainID)
digest, err := digestEIP712(eip712ConfigHash, d, o)
if err != nil {
return err
}
Expand All @@ -191,7 +180,7 @@ func (d Definition) VerifySignatures() error {
}

// Check that we have a valid enr signature for each operator.
digest, err = digestEIP712(eip712TypeENR, o.ENR, chainID)
digest, err = digestEIP712(eip712ENR, d, o)
if err != nil {
return err
}
Expand Down
106 changes: 106 additions & 0 deletions cluster/eip712sigs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright © 2022 Obol Labs Inc.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <http://www.gnu.org/licenses/>.

package cluster

import (
"crypto/ecdsa"

ethmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core/apitypes"

"github.com/obolnetwork/charon/app/errors"
"github.com/obolnetwork/charon/eth2util"
)

// eip712Type defines the EIP712 (https://eips.ethereum.org/EIPS/eip-712) Primary type and the Message field and value.
type eip712Type struct {
PrimaryType string
Field string
ValueFunc func(Definition, Operator) string
}

var (
// eip712ConfigHash defines the EIP712 structure of the config signature.
eip712ConfigHash = eip712Type{
PrimaryType: "ConfigHash",
Field: "config_hash",
ValueFunc: func(definition Definition, _ Operator) string {
return to0xHex(definition.ConfigHash)
},
}

// eip712ENR defines the EIP712 structure of the enr signature.
eip712ENR = eip712Type{
PrimaryType: "ENR",
Field: "enr",
ValueFunc: func(_ Definition, operator Operator) string {
return operator.ENR
},
}
)

// digestEIP712 returns the digest for the EIP712 structured type for the provided definition and operator.
func digestEIP712(typ eip712Type, def Definition, operator Operator) ([]byte, error) {
chainID, err := eth2util.ForkVersionToChainID(def.ForkVersion)
if err != nil {
return nil, err
}

data := apitypes.TypedData{
Types: apitypes.Types{
"EIP712Domain": []apitypes.Type{
{Name: "name", Type: "string"},
{Name: "version", Type: "string"},
{Name: "chainId", Type: "uint256"},
},
typ.PrimaryType: []apitypes.Type{
{Name: typ.Field, Type: "string"},
},
},
PrimaryType: typ.PrimaryType,
Message: apitypes.TypedDataMessage{
typ.Field: typ.ValueFunc(def, operator),
},
Domain: apitypes.TypedDataDomain{
Name: "Obol",
Version: "1",
ChainId: ethmath.NewHexOrDecimal256(chainID),
},
}

digest, _, err := apitypes.TypedDataAndHash(data)
if err != nil {
return nil, errors.Wrap(err, "hash EIP712")
}

return digest, nil
}

// signEIP712 returns the EIP712 signature for the primary type.
func signEIP712(secret *ecdsa.PrivateKey, typ eip712Type, def Definition, operator Operator) ([]byte, error) {
digest, err := digestEIP712(typ, def, operator)
if err != nil {
return nil, err
}

sig, err := crypto.Sign(digest, secret)
if err != nil {
return nil, errors.Wrap(err, "sign EIP712")
}

return sig, nil
}
66 changes: 3 additions & 63 deletions cluster/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ import (
"strings"

"github.com/coinbase/kryptology/pkg/signatures/bls/bls_sig"
ethmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
ssz "github.com/ferranbt/fastssz"

"github.com/obolnetwork/charon/app/errors"
Expand All @@ -44,17 +42,6 @@ const (
k1RecIdx = 64
)

// eip712Type ties the EIP712 Primary type to its Message field.
type eip712Type struct {
PrimaryType string
Field string
}

var (
eip712TypeConfigHash = eip712Type{"ConfigHash", "config_hash"}
eip712TypeENR = eip712Type{"ENR", "enr"}
)

// uuid returns a random uuid.
func uuid(random io.Reader) string {
b := make([]byte, 16)
Expand Down Expand Up @@ -95,69 +82,22 @@ func verifySig(expectedAddr string, digest []byte, sig []byte) (bool, error) {
}

// signOperator returns the operator with signed config hash and enr.
func signOperator(secret *ecdsa.PrivateKey, operator Operator, configHash [32]byte, chainID int64) (Operator, error) {
func signOperator(secret *ecdsa.PrivateKey, def Definition, operator Operator) (Operator, error) {
var err error

operator.ConfigSignature, err = signEIP712(secret, eip712TypeConfigHash, to0xHex(configHash[:]), chainID)
operator.ConfigSignature, err = signEIP712(secret, eip712ConfigHash, def, operator)
if err != nil {
return Operator{}, err
}

operator.ENRSignature, err = signEIP712(secret, eip712TypeENR, operator.ENR, chainID)
operator.ENRSignature, err = signEIP712(secret, eip712ENR, def, operator)
if err != nil {
return Operator{}, err
}

return operator, nil
}

// signEIP712 returns the EIP712 signature for the primary type.
func signEIP712(secret *ecdsa.PrivateKey, typ eip712Type, fieldValue string, chainID int64) ([]byte, error) {
digest, err := digestEIP712(typ, fieldValue, chainID)
if err != nil {
return nil, err
}

sig, err := crypto.Sign(digest, secret)
if err != nil {
return nil, errors.Wrap(err, "sign EIP712")
}

return sig, nil
}

// digestEIP712 returns the EIP712 (https://eips.ethereum.org/EIPS/eip-712) digest for the primary type.
func digestEIP712(typ eip712Type, fieldValue string, chainID int64) ([]byte, error) {
data := apitypes.TypedData{
Types: apitypes.Types{
"EIP712Domain": []apitypes.Type{
{Name: "name", Type: "string"},
{Name: "version", Type: "string"},
{Name: "chainId", Type: "uint256"},
},
typ.PrimaryType: []apitypes.Type{
{Name: typ.Field, Type: "string"},
},
},
PrimaryType: typ.PrimaryType,
Message: apitypes.TypedDataMessage{
typ.Field: fieldValue,
},
Domain: apitypes.TypedDataDomain{
Name: "Obol",
Version: "1",
ChainId: ethmath.NewHexOrDecimal256(chainID),
},
}

digest, _, err := apitypes.TypedDataAndHash(data)
if err != nil {
return nil, errors.Wrap(err, "hash EIP712")
}

return digest, nil
}

// aggSign returns a bls aggregate signatures of the message signed by all the shares.
func aggSign(secrets [][]*bls_sig.SecretKeyShare, message []byte) ([]byte, error) {
var sigs []*bls_sig.Signature
Expand Down
25 changes: 8 additions & 17 deletions cluster/test_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/stretchr/testify/require"

"github.com/obolnetwork/charon/eth2util"
"github.com/obolnetwork/charon/p2p"
"github.com/obolnetwork/charon/tbls"
"github.com/obolnetwork/charon/testutil"
Expand Down Expand Up @@ -106,6 +105,9 @@ func NewForT(t *testing.T, dv, k, n, seed int, opts ...func(*Definition)) (Lock,
op := Operator{
Address: addr.Hex(),
ENR: enrStr,
// Set to empty signatures instead of nil so aligned with unmarshalled json
ENRSignature: ethHex{},
ConfigSignature: ethHex{},
}

ops = append(ops, op)
Expand All @@ -121,28 +123,17 @@ func NewForT(t *testing.T, dv, k, n, seed int, opts ...func(*Definition)) (Lock,
opt(&def)
}

// Definition version prior to v1.3.0 don't support EIP712 signatures.
if def.Version == "v1.0.0" || def.Version == "v1.1.0" || def.Version == "v1.2.0" {
for i := range def.Operators {
def.Operators[i].ConfigSignature = []byte{}
def.Operators[i].ENRSignature = []byte{}
}
} else {
confHash, err := hashDefinition(def, true)
require.NoError(t, err)

chainID, err := eth2util.ForkVersionToChainID(def.ForkVersion)
require.NoError(t, err)
def, err = def.SetDefinitionHashes()
require.NoError(t, err)

// Definition version prior to v1.3.0 don't support EIP712 signatures.
if supportEIP712Sigs(def.Version) {
for i := 0; i < n; i++ {
def.Operators[i], err = signOperator(p2pKeys[i], def.Operators[i], confHash, chainID)
def.Operators[i], err = signOperator(p2pKeys[i], def, def.Operators[i])
require.NoError(t, err)
}
}

def, err = def.SetDefinitionHashes()
require.NoError(t, err)

lock := Lock{
Definition: def,
Validators: vals,
Expand Down
Loading

0 comments on commit 772f768

Please sign in to comment.