From 56e78948ee6aa01f8b64f825d51dde1470a6482a Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Wed, 10 Jul 2024 18:36:18 -0500 Subject: [PATCH] Implement eth_signTypedData_v4 --- pkg/types/encoding/eip712.go | 244 ++++++++++++++--- protocol/signature.go | 20 -- protocol/signature_eip712.go | 16 +- protocol/signature_test.go | 54 +++- test/cmd/eth_signTypedData/.gitignore | 1 + test/cmd/eth_signTypedData/execute.sh | 10 + test/cmd/eth_signTypedData/main.js | 6 + test/cmd/eth_signTypedData/package-lock.json | 261 +++++++++++++++++++ test/cmd/eth_signTypedData/package.json | 5 + test/cmd/eth_signTypedData/test.js | 75 ++++++ 10 files changed, 621 insertions(+), 71 deletions(-) create mode 100644 test/cmd/eth_signTypedData/.gitignore create mode 100755 test/cmd/eth_signTypedData/execute.sh create mode 100644 test/cmd/eth_signTypedData/main.js create mode 100644 test/cmd/eth_signTypedData/package-lock.json create mode 100644 test/cmd/eth_signTypedData/package.json create mode 100644 test/cmd/eth_signTypedData/test.js diff --git a/pkg/types/encoding/eip712.go b/pkg/types/encoding/eip712.go index 93fa88734..9cd847c2d 100644 --- a/pkg/types/encoding/eip712.go +++ b/pkg/types/encoding/eip712.go @@ -11,7 +11,6 @@ import ( "encoding/binary" "encoding/hex" "encoding/json" - "errors" "fmt" "math/big" "reflect" @@ -50,8 +49,9 @@ var Eip712Domain = EIP712Domain{ } type Eip712Encoder struct { - hasher func(v interface{}) ([]byte, error) - types func(ret map[string][]*TypeField, v interface{}, fieldType string) error + hasher func(v interface{}) ([]byte, error) + resolver func(any, string) (eipResolvedValue, error) + types func(ret map[string][]*TypeField, v interface{}, fieldType string) error } var eip712EncoderMap map[string]Eip712Encoder @@ -250,49 +250,170 @@ func (td *TypeDefinition) types(ret map[string][]*TypeField, d interface{}, type return nil } -func (td *TypeDefinition) hash(v interface{}, typeName string) ([]byte, error) { - data, ok := v.(map[string]interface{}) +func (td *TypeDefinition) Resolve(v any, typeName string) (*eipResolvedStruct, error) { + data, ok := v.(map[string]any) if !ok { return nil, fmt.Errorf("cannot hash type definition with invalid interface %T", v) } - //define the type structure - var header bytes.Buffer - var body bytes.Buffer - - //now loop through the fields and either encode the value or recursively dive into more types - first := true - //the stripping shouldn't be necessary, but do it as a precaution - strippedType, _ := stripSlice(typeName) - header.WriteString(strippedType + "(") + var fields []*eipResolvedField for _, field := range *td.Fields { value, ok := data[field.Name] if !ok { continue } - delete(data, field.Name) + r, err := field.encoder.resolver(value, field.Type) + if err != nil { + return nil, err + } + + fields = append(fields, &eipResolvedField{ + Name: field.Name, + Type: field.Type, + Value: r, + }) + } + + return &eipResolvedStruct{ + Type: typeName, + Fields: fields, + }, nil +} + +func (td *TypeDefinition) hash(v interface{}, typeName string) ([]byte, error) { + e, err := td.Resolve(v, typeName) + if err != nil { + return nil, err + } + return e.Hash() +} + +type eipResolvedValue interface { + Hash() ([]byte, error) + header(map[string]string) + types(map[string][]*TypeField) +} + +type eipResolvedStruct struct { + Type string + Fields []*eipResolvedField +} + +type eipResolvedField struct { + Name string + Type string + Value eipResolvedValue +} + +type eipResolvedArray []eipResolvedValue + +type eipResolvedAtomic struct { + Value any + hasher func(any) ([]byte, error) +} + +func (e *eipResolvedStruct) Hash() ([]byte, error) { + //the stripping shouldn't be necessary, but do it as a precaution + strippedType, _ := stripSlice(e.Type) + + deps := map[string]string{} + e.header(deps) + + var header bytes.Buffer + header.WriteString(deps[strippedType]) + delete(deps, strippedType) + + var depNames []string + for name := range deps { + depNames = append(depNames, name) + } + sort.Strings(depNames) + for _, name := range depNames { + header.WriteString(deps[name]) + } + + var buf bytes.Buffer + buf.Write(keccak256(header.Bytes())) + + //now loop through the fields and either encode the value or recursively dive into more types + for _, field := range e.Fields { //now run the hasher - encodedValue, err := field.encoder.hasher(value) + encodedValue, err := field.Value.Hash() if err != nil { return nil, err } - if !first { + buf.Write(encodedValue) + } + + return keccak256(buf.Bytes()), nil +} + +func (e *eipResolvedStruct) header(ret map[string]string) { + //the stripping shouldn't be necessary, but do it as a precaution + strippedType, _ := stripSlice(e.Type) + + //define the type structure + var header strings.Builder + header.WriteString(strippedType + "(") + for i, field := range e.Fields { + if i > 0 { header.WriteString(",") } header.WriteString(field.Type + " " + field.Name) - body.Write(encodedValue) - first = false + field.Value.header(ret) } header.WriteString(")") + ret[strippedType] = header.String() +} + +func (e *eipResolvedStruct) Types() map[string][]*TypeField { + ret := map[string][]*TypeField{} + e.types(ret) + return ret +} + +func (e *eipResolvedStruct) types(ret map[string][]*TypeField) { + var fields []*TypeField + for _, f := range e.Fields { + fields = append(fields, &TypeField{Name: f.Name, Type: f.Type}) + f.Value.types(ret) + } + name, _ := stripSlice(e.Type) + ret[name] = fields +} + +func (e eipResolvedArray) Hash() ([]byte, error) { + var buf bytes.Buffer + for _, v := range e { + hash, err := v.Hash() + if err != nil { + return nil, err + } + _, _ = buf.Write(hash) + } + return keccak256(buf.Bytes()), nil +} - if len(data) > 0 { - return nil, errors.New("eip712 payload contains unknown fields") +func (e eipResolvedArray) header(ret map[string]string) { + for _, v := range e { + v.header(ret) } +} + +func (e eipResolvedArray) types(ret map[string][]*TypeField) { + for _, v := range e { + v.types(ret) + } +} - return keccak256(append(keccak256(header.Bytes()), body.Bytes()...)), nil +func (e *eipResolvedAtomic) Hash() ([]byte, error) { + return e.hasher(e.Value) } +func (e *eipResolvedAtomic) header(map[string]string) {} +func (e *eipResolvedAtomic) types(map[string][]*TypeField) {} + func (t *TypeField) types(ret map[string][]*TypeField, v interface{}, fieldType string) error { if t.encoder.types != nil { //process more complex type @@ -302,7 +423,7 @@ func (t *TypeField) types(ret map[string][]*TypeField, v interface{}, fieldType } func NewEncoder[T any](hasher func(T) ([]byte, error), types func(ret map[string][]*TypeField, v interface{}, typeField string) error) Eip712Encoder { - return Eip712Encoder{func(v interface{}) ([]byte, error) { + hasher2 := func(v interface{}) ([]byte, error) { // JSON always decodes numbers as floats if u, ok := v.(float64); ok { var z T @@ -319,7 +440,11 @@ func NewEncoder[T any](hasher func(T) ([]byte, error), types func(ret map[string return nil, fmt.Errorf("eip712 value of type %T does not match type field", v) } return hasher(t) - }, types} + } + resolver := func(v any, _ string) (eipResolvedValue, error) { + return &eipResolvedAtomic{v, hasher2}, nil + } + return Eip712Encoder{hasher2, resolver, types} } func NewTypeField(n string, tp string) *TypeField { @@ -389,6 +514,59 @@ func NewTypeField(n string, tp string) *TypeField { return nil, err } return b, nil + }, func(v any, typeName string) (eipResolvedValue, error) { + strippedType, slices := stripSlice(tp) + encoder, ok := eip712EncoderMap[strippedType] + if ok { + if slices > 0 { + vv, ok := v.([]interface{}) + if !ok { + return nil, fmt.Errorf("eip712 field %s is not of an array of interfaces", n) + } + var array eipResolvedArray + for _, vvv := range vv { + r, err := encoder.resolver(vvv, tp) + if err != nil { + return nil, err + } + array = append(array, r) + } + return array, nil + } + return encoder.resolver(v, tp) + } + + //from here on down we are expecting a struct + if slices > 0 { + //we expect a slice + vv, ok := v.([]interface{}) + if !ok { + return nil, fmt.Errorf("eip712 field %s is not of an array of interfaces", n) + } + var array eipResolvedArray + for _, vvv := range vv { + //now run the hasher for the type + // look for encoder, if we don't have one, call the types encoder + fields, ok := SchemaDictionary[strippedType] + if !ok { + return nil, fmt.Errorf("eip712 field %s", tp) + } + r, err := fields.Resolve(vvv, tp) + if err != nil { + return nil, err + } + array = append(array, r) + } + return array, nil + } + + //if we get here, we are expecting a struct + fields, ok := SchemaDictionary[strippedType] + if !ok { + return nil, fmt.Errorf("eip712 field %s", tp) + } + + return fields.Resolve(v, tp) }, func(ret map[string][]*TypeField, v interface{}, fieldType string) error { strippedType, slices := stripSlice(tp) encoder, ok := eip712EncoderMap[strippedType] @@ -480,17 +658,21 @@ func RegisterTypeDefinition(tf *[]*TypeField, aliases ...string) { } func Eip712Hash(v map[string]interface{}, typeName string, td *TypeDefinition) ([]byte, error) { - messageHash, err := td.hash(v, typeName) + r, err := td.Resolve(v, typeName) + if err != nil { + return nil, err + } + messageHash, err := r.Hash() if err != nil { return nil, err } - return keccak256(append(EIP712DomainHash, messageHash...)), nil -} -func Eip712Types(v map[string]any, typeName string, td *TypeDefinition) (map[string][]*TypeField, error) { - ret := map[string][]*TypeField{} - err := td.types(ret, v, typeName) - return ret, err + var buf bytes.Buffer + buf.WriteByte(0x19) + buf.WriteByte(0x01) + buf.Write(EIP712DomainHash) + buf.Write(messageHash) + return keccak256(buf.Bytes()), nil } func Eip712DomainType() *TypeDefinition { diff --git a/protocol/signature.go b/protocol/signature.go index 29eed1340..d829f63ae 100644 --- a/protocol/signature.go +++ b/protocol/signature.go @@ -126,26 +126,6 @@ func PublicKeyHash(key []byte, typ SignatureType) ([]byte, error) { } } -// generates privatekey and compressed public key -func SECP256K1Keypair() (privKey []byte, pubKey []byte) { - priv, _ := btc.NewPrivateKey(btc.S256()) - - privKey = priv.Serialize() - _, pub := btc.PrivKeyFromBytes(btc.S256(), privKey) - pubKey = pub.SerializeCompressed() - return privKey, pubKey -} - -// generates privatekey and Un-compressed public key -func SECP256K1UncompressedKeypair() (privKey []byte, pubKey []byte) { - priv, _ := btc.NewPrivateKey(btc.S256()) - - privKey = priv.Serialize() - _, pub := btc.PrivKeyFromBytes(btc.S256(), privKey) - pubKey = pub.SerializeUncompressed() - return privKey, pubKey -} - func BTCHash(pubKey []byte) []byte { hasher := ripemd160.New() hash := sha256.Sum256(pubKey[:]) diff --git a/protocol/signature_eip712.go b/protocol/signature_eip712.go index eef09f775..eba58b710 100644 --- a/protocol/signature_eip712.go +++ b/protocol/signature_eip712.go @@ -45,6 +45,10 @@ func MarshalEip712(txn *Transaction, sig Signature) (ret []byte, err error) { if err != nil { return nil, err } + r, err := NewEip712TransactionDefinition(txn).Resolve(jtx, "Transaction") + if err != nil { + return nil, err + } // Construct the wallet RPC call type eip712 struct { @@ -56,16 +60,8 @@ func MarshalEip712(txn *Transaction, sig Signature) (ret []byte, err error) { e := eip712{} e.PrimaryType = "Transaction" e.Domain = encoding.Eip712Domain - - // Reformat the message JSON to be compatible with Ethereum - td := NewEip712TransactionDefinition(txn) - formatEIP712Message(jtx, td) e.Message = jtx - - e.Types, err = encoding.Eip712Types(jtx, "Transaction", td) - if err != nil { - return nil, err - } + e.Types = r.Types() e.Types["EIP712Domain"] = *encoding.Eip712DomainType().Fields return json.Marshal(e) @@ -131,6 +127,8 @@ func makeEIP712Message(txn *Transaction, sig Signature) (map[string]any, error) } jtx["signature"] = jsig + // Reformat the message JSON to be compatible with Ethereum + formatEIP712Message(jtx, NewEip712TransactionDefinition(txn)) return jtx, nil } diff --git a/protocol/signature_test.go b/protocol/signature_test.go index 7c8f15b0e..a27984dd7 100644 --- a/protocol/signature_test.go +++ b/protocol/signature_test.go @@ -7,6 +7,7 @@ package protocol_test import ( + "bytes" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -15,10 +16,15 @@ import ( "crypto/sha256" "crypto/x509" "crypto/x509/pkix" + "encoding/binary" "encoding/hex" "encoding/pem" + "errors" "fmt" "math/big" + badrand "math/rand" + "os" + "os/exec" "testing" "time" @@ -26,6 +32,7 @@ import ( "github.com/btcsuite/btcutil/base58" eth "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" + "gitlab.com/accumulatenetwork/accumulate/internal/database/record" "gitlab.com/accumulatenetwork/accumulate/pkg/build" "gitlab.com/accumulatenetwork/accumulate/pkg/types/address" "gitlab.com/accumulatenetwork/accumulate/pkg/url" @@ -491,6 +498,17 @@ func TestTypesFromCerts(t *testing.T) { } } +// generates privatekey and compressed public key +func NewSECP256K1(seed ...any) *btc.PrivateKey { + hash := record.NewKey(seed...).Hash() + src := badrand.NewSource(int64(binary.BigEndian.Uint64(hash[:]))) + priv, err := ecdsa.GenerateKey(btc.S256(), badrand.New(src)) + if err != nil { + panic(err) + } + return (*btc.PrivateKey)(priv) +} + func TestEip712TypedDataSignature(t *testing.T) { txn := &Transaction{} err := txn.UnmarshalJSON([]byte(`{ @@ -516,8 +534,8 @@ func TestEip712TypedDataSignature(t *testing.T) { } // Sign the transaction - priv, _ := SECP256K1Keypair() - require.NoError(t, SignEip712TypedData(eip712sig, priv, nil, txn)) + priv := NewSECP256K1(t.Name()) + require.NoError(t, SignEip712TypedData(eip712sig, priv.Serialize(), nil, txn)) // Verify the signature require.True(t, eip712sig.Verify(nil, txn)) @@ -552,16 +570,25 @@ func TestEIP712DelegatedKeyPageUpdate(t *testing.T) { } // Sign the transaction - priv, _ := SECP256K1Keypair() - require.NoError(t, SignEip712TypedData(inner, priv, outer, txn)) + priv := NewSECP256K1(t.Name()) + require.NoError(t, SignEip712TypedData(inner, priv.Serialize(), outer, txn)) // Verify the signature require.True(t, outer.Verify(nil, txn)) } func TestEIP712MessageForWallet(t *testing.T) { + // Verify the system has node + _, err := exec.LookPath("node") + if err != nil { + if !errors.Is(err, exec.ErrNotFound) || os.Getenv("CI") == "true" { + require.NoError(t, err) + } + t.Skip("Cannot locate node binary") + } + txn := &Transaction{} - err := txn.UnmarshalJSON([]byte(`{ + err = txn.UnmarshalJSON([]byte(`{ "header": { "principal": "acc://adi.acme/ACME" }, @@ -575,10 +602,9 @@ func TestEIP712MessageForWallet(t *testing.T) { }`)) require.NoError(t, err) - pub, err := hex.DecodeString("04c4755e0a7a0f7082749bf46cdae4fcddb784e11428446a01478d656f588f94c17d02f3312b43364a0c480d628483c4fb4e3e9f687ac064717d90fdc42cfb6e0e") - require.NoError(t, err) + priv := NewSECP256K1(t.Name()) sig := &Eip712TypedDataSignature{ - PublicKey: pub, + PublicKey: eth.FromECDSAPub(&priv.PublicKey), Signer: url.MustParse("acc://adi.acme/book/1"), SignerVersion: 1, Timestamp: 1720564975623, @@ -588,10 +614,16 @@ func TestEIP712MessageForWallet(t *testing.T) { b, err := MarshalEip712(txn, sig) require.NoError(t, err) - fmt.Printf("%s\n", b) - // Result from metamask - sig.Signature, err = hex.DecodeString("d420cddc64babaa548a09a9b05ae4b5cab6ab78fcb715870bd3a794be84b608763f52b044f672e4e2152beb42dcea00b8b5e36a1eecf6aa26ae62436c6e6d70f1b") + cmd := exec.Command("../test/cmd/eth_signTypedData/execute.sh", hex.EncodeToString(priv.Serialize()), string(b)) + cmd.Stderr = os.Stderr + out, err := cmd.Output() + require.NoError(t, err) + + out = bytes.TrimSpace(out) + out = bytes.TrimPrefix(out, []byte("0x")) + sig.Signature = make([]byte, len(out)/2) + _, err = hex.Decode(sig.Signature, out) require.NoError(t, err) require.True(t, sig.Verify(nil, txn)) } diff --git a/test/cmd/eth_signTypedData/.gitignore b/test/cmd/eth_signTypedData/.gitignore new file mode 100644 index 000000000..30bc16279 --- /dev/null +++ b/test/cmd/eth_signTypedData/.gitignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/test/cmd/eth_signTypedData/execute.sh b/test/cmd/eth_signTypedData/execute.sh new file mode 100755 index 000000000..ef94eea78 --- /dev/null +++ b/test/cmd/eth_signTypedData/execute.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# CD to the directory containing this file +cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null + +# Make sure dependencies are installed +npm ci &> /dev/null + +# Run the script +node main.js "$@" \ No newline at end of file diff --git a/test/cmd/eth_signTypedData/main.js b/test/cmd/eth_signTypedData/main.js new file mode 100644 index 000000000..7526bdda1 --- /dev/null +++ b/test/cmd/eth_signTypedData/main.js @@ -0,0 +1,6 @@ +const eth = require('@metamask/eth-sig-util'); + +const privateKey = Buffer.from(process.argv[2], 'hex'); +const data = JSON.parse(process.argv[3]); +const sig = eth.signTypedData({ privateKey, data, version: 'V4' }); +process.stdout.write(sig); \ No newline at end of file diff --git a/test/cmd/eth_signTypedData/package-lock.json b/test/cmd/eth_signTypedData/package-lock.json new file mode 100644 index 000000000..3a1eb7f4a --- /dev/null +++ b/test/cmd/eth_signTypedData/package-lock.json @@ -0,0 +1,261 @@ +{ + "name": "eth_signTypedData", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@metamask/eth-sig-util": "^7.0.2" + } + }, + "node_modules/@ethereumjs/common": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "@ethereumjs/util": "^8.1.0", + "crc-32": "^1.2.0" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "4.0.1", + "license": "MPL-2.0", + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "4.2.0", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/common": "^3.2.0", + "@ethereumjs/rlp": "^4.0.1", + "@ethereumjs/util": "^8.1.0", + "ethereum-cryptography": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/util": { + "version": "8.1.0", + "license": "MPL-2.0", + "dependencies": { + "@ethereumjs/rlp": "^4.0.1", + "ethereum-cryptography": "^2.0.0", + "micro-ftch": "^0.3.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@metamask/abi-utils": { + "version": "2.0.4", + "license": "(Apache-2.0 AND MIT)", + "dependencies": { + "@metamask/superstruct": "^3.1.0", + "@metamask/utils": "^9.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/abi-utils/node_modules/@metamask/utils": { + "version": "9.0.0", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/eth-sig-util": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-7.0.2.tgz", + "integrity": "sha512-DhTDMNEtED0ihIc4Tysm6qUJTvArCdgSTeeJWdo526W/cAk5mrSAvEYYgv8idAiBumDtcPWGimMTaB7MvY64bg==", + "dependencies": { + "@ethereumjs/util": "^8.1.0", + "@metamask/abi-utils": "^2.0.2", + "@metamask/utils": "^8.1.0", + "@scure/base": "~1.1.3", + "ethereum-cryptography": "^2.1.2", + "tweetnacl": "^1.0.3" + }, + "engines": { + "node": "^16.20 || ^18.16 || >=20" + } + }, + "node_modules/@metamask/superstruct": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/utils": { + "version": "8.5.0", + "license": "ISC", + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.7", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/micro-ftch": { + "version": "0.3.1", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/pony-cause": { + "version": "2.1.11", + "license": "0BSD", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "license": "Unlicense" + }, + "node_modules/uuid": { + "version": "9.0.1", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/test/cmd/eth_signTypedData/package.json b/test/cmd/eth_signTypedData/package.json new file mode 100644 index 000000000..4861e9bac --- /dev/null +++ b/test/cmd/eth_signTypedData/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@metamask/eth-sig-util": "^7.0.2" + } +} diff --git a/test/cmd/eth_signTypedData/test.js b/test/cmd/eth_signTypedData/test.js new file mode 100644 index 000000000..60196c03a --- /dev/null +++ b/test/cmd/eth_signTypedData/test.js @@ -0,0 +1,75 @@ +const eth = require("@metamask/eth-sig-util"); + +const privateKey = 'ed8167d5c93177cd9090f2e71e6034a11f8bd6e383f4af28864318481e148581' +const initiator = '0x983efb69d2d8dcfe952850f8469518b93908ee1e0eabd9886f58fee603c4c912' +const publicKey = '0x04be413510976c868b35aa4d6da89ebe0873ad351bcb75438696fd1f5a6a1e4eac2d2ed8c34a547d83f2dc7aca713d2e3e180c70110bb2faf4a12cee752a701706' +// const privateKey = '05ed8167d5c93177cd9090f2e71e6034a11f8bd6e383f4af28864318481e1485' +// const initiator = '0xee42393d7b1fc5013e77f633bd21960fc47b619222164617c03e7946b6a8d786' +// const publicKey = '0x04fb8c16cf3165ac6cd2188aa2014dbc2528e66ce06c881fa1f99fcc6598bf13dc5091400e4e335db65dca5130f037e367b138573f2808665772ffb35208943606' + +const data = { + types: { + EIP712Domain: [ + { name: "chainId", type: "uint256" }, + { name: "name", type: "string" }, + { name: "version", type: "string" }, + ], + SignatureMetadata: [ + { name: "publicKey", type: "bytes" }, + { name: "signer", type: "string" }, + { name: "signerVersion", type: "uint64" }, + { name: "timestamp", type: "uint64" }, + { name: "type", type: "string" }, + ], + TokenRecipient: [ + { name: "amount", type: "uint256" }, + { name: "url", type: "string" }, + ], + Transaction: [ + { name: "header", type: "TransactionHeader" }, + { name: "body", type: "sendTokens" }, + { name: "signature", type: "SignatureMetadata" }, + ], + TransactionHeader: [ + { name: "initiator", type: "bytes32" }, + { name: "principal", type: "string" }, + ], + sendTokens: [ + { name: "to", type: "TokenRecipient[]" }, + { name: "type", type: "string" }, + ], + }, + primaryType: "Transaction", + domain: { name: "Accumulate", version: "1.0.0", chainId: 1 }, + message: { + body: { + to: [{ amount: "10000000000", url: "acc://other.acme/ACME" }], + type: "sendTokens", + }, + header: { + initiator, + principal: "acc://adi.acme/ACME", + }, + signature: { + publicKey, + signer: "acc://adi.acme/book/1", + signerVersion: 1, + timestamp: 1720564975623, + type: "eip712TypedData", + }, + }, +}; +const sig = eth.TypedDataUtils.eip712Hash(data, "V4").toString("hex"); +// const sig = eth.signTypedData({ privateKey, data, version: 'V4' }) + +process.stdout.write(sig); + +/* +process.stderr.write(`${encodeType(primaryType, types)}\n`) +for (const i in encodedValues) { + const b = abi_utils_1.encode([encodedTypes[i]], [encodedValues[i]]) + process.stderr.write(` ${i == 0 ? ' ' : '+'} ${Buffer.from(b).toString('hex')}\n`) +} +const enc = (0, util_1.arrToBufArr)((0, abi_utils_1.encode)(encodedTypes, encodedValues)); +process.stderr.write(` = ${Buffer.from(keccak_1.keccak256(enc)).toString('hex')}\n\n`) +*/