Skip to content

Commit

Permalink
Add SignTypedData to native Keystore interface (ethereum#1220)
Browse files Browse the repository at this point in the history
* Add SignTypedData to native Keystore interface

* Add simple smoke test for the signTypedData method in the native interface
  • Loading branch information
hbandura authored Nov 12, 2020
1 parent 416e518 commit c767f8c
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 0 deletions.
Binary file modified geth-sources.jar
Binary file not shown.
28 changes: 28 additions & 0 deletions mobile/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
package geth

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

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core"
)

const (
Expand Down Expand Up @@ -120,6 +123,31 @@ func (ks *KeyStore) SignHash(address *Address, hash []byte) (signature []byte, _
return ks.keystore.SignHash(accounts.Account{Address: address.address}, common.CopyBytes(hash))
}

// SignTypedData signs EIP-712 conformant typed data
// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
func (ks *KeyStore) SignTypedData(account *Account, typedDataJSON []byte) ([]byte, error) {
var typedData core.TypedData
err := json.Unmarshal(typedDataJSON, &typedData)
if err != nil {
return nil, err
}
domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
if err != nil {
return nil, err
}
typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
if err != nil {
return nil, err
}
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
sighash := crypto.Keccak256(rawData)
signature, err := ks.keystore.SignHash(account.account, sighash)
if err != nil {
return nil, err
}
return signature, nil
}

// SignTx signs the given transaction with the requested account.
func (ks *KeyStore) SignTx(account *Account, tx *Transaction, chainID *BigInt) (*Transaction, error) {
if chainID == nil { // Null passed from mobile app
Expand Down
123 changes: 123 additions & 0 deletions mobile/accounts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package geth

import (
"io/ioutil"
"os"
"testing"

"github.com/ethereum/go-ethereum/accounts/keystore"
)

const jsonTypedData = `
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "test",
"type": "uint8"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "string"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": "1",
"verifyingContract": "0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"test": 3,
"wallet": "0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"test": 4,
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
`

const scryptN = 2
const scryptP = 1

// copied from the keystore_test.go file in the keystore package
func tmpKeyStore(t *testing.T, encrypted bool) (string, *keystore.KeyStore) {
d, err := ioutil.TempDir("", "eth-keystore-test")
if err != nil {
t.Fatal(err)
}
newKs := keystore.NewPlaintextKeyStore
if encrypted {
newKs = func(kd string) *keystore.KeyStore {
return keystore.NewKeyStore(kd, scryptN, scryptP)
}
}
return d, newKs(d)
}

func TestSignTypedData(t *testing.T) {
dir, ks := tmpKeyStore(t, true)
defer os.RemoveAll(dir)

pass := "" // not used but required by API
a1, err := ks.NewAccount(pass)
if err != nil {
t.Fatal(err)
}
nks := &KeyStore{keystore: ks}
nAcc := &Account{account: a1}
if err := nks.Unlock(nAcc, ""); err != nil {
t.Fatal(err)
}
signature, err := nks.SignTypedData(nAcc, []byte(jsonTypedData))
if err != nil {
t.Fatal(err)
}
if signature == nil || len(signature) != 65 {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
}
}

0 comments on commit c767f8c

Please sign in to comment.