Implementing UnmarshalJSON() for TypedData

This commit is contained in:
Paul Berg 2018-10-10 14:53:53 -07:00
parent a16c250409
commit e440c25a2b
7 changed files with 230 additions and 12 deletions

11
cmd/clef/audit.log Normal file
View file

@ -0,0 +1,11 @@
t=2018-10-10T14:45:26-0700 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59942\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c}
t=2018-10-10T14:45:37-0700 lvl=info msg=SignStructuredData api=signer type=response data= error=nil
t=2018-10-10T14:45:55-0700 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-10-10T14:45:55-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59956\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c}
t=2018-10-10T14:46:18-0700 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-10-10T14:46:50-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59967\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data={PrimaryType:0x4d61696c}
t=2018-10-10T14:51:32-0700 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-10-10T14:51:59-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60000\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}"
t=2018-10-10T14:52:51-0700 lvl=info msg=Configured api=signer audit log=audit.log
t=2018-10-10T14:52:56-0700 lvl=info msg=SignStructuredData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60017\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 [chksum ok]" data="{Hash:[226 140 80 200 149 243 254 73 125 193 200 45 83 189 176 154 4 204 167 86 220 130 208 11 170 238 116 129 150 85 62 148]}"

View file

@ -327,9 +327,10 @@ func initialize(c *cli.Context) error {
// If using the stdioui, we can't do the 'confirm'-flow
fmt.Fprintf(logOutput, legalWarning)
} else {
if !confirm(legalWarning) {
return fmt.Errorf("aborted by user")
}
// Temporarily disabled while in development
//if !confirm(legalWarning) {
// return fmt.Errorf("aborted by user")
//}
}
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true))))
@ -552,14 +553,15 @@ func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) {
var password string
// If ui is not nil, get the password from ui.
if ui != nil {
resp, err := ui.OnInputRequired(core.UserInputRequest{
Title: "Master Password",
Prompt: "Please enter the password to decrypt the master seed",
IsPassword: true})
if err != nil {
return nil, err
}
password = resp.Text
// Temporarily disabled while in development
//resp, err := ui.OnInputRequired(core.UserInputRequest{
// Title: "Master Password",
// Prompt: "Please enter the password to decrypt the master seed",
// IsPassword: true})
//if err != nil {
// return nil, err
//}
//password = resp.Text
} else {
password = getPassPhrase("Decrypt master seed of clef", false)
}

View file

@ -209,3 +209,8 @@ type GasEstimator interface {
type PendingStateEventer interface {
SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error)
}
// TypedData is the standard for EIP-712 signed data.
type TypedData struct {
Hash *common.Hash `json:"hash"`
}

View file

@ -52,6 +52,8 @@ type ExternalAPI interface {
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// SignData - request to sign the given data (plus prefix)
SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
// SignStructuredData - request to sign the given structured data (plus prefix)
SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error)
// EcRecover - recover public key from given message and signature
EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
// Export - request to export an account

134
signer/core/api_layout.md Normal file
View file

@ -0,0 +1,134 @@
# Specs
`encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)`
- data adheres to 𝕊, a structure defined in the rigorous eip-712
- `\x01` is needed to comply with EIP-191
- `domainSeparator` and `hashStruct` are defined below
## A) domainSeparator
`domainSeparator = hashStruct(eip712Domain)`
<br/>
<br/>
Struct named `EIP712Domain` with one or more of the below fields:
- `string name`
- `string version`
- `uint256 chainId`, as per EIP-155
- `address verifyingContract`
- `bytes32 salt`
## B) hashStruct
`hashStruct(s : 𝕊) = keccak256(typeHash ‖ encodeData(s))`
<br/>
`typeHash = keccak256(encodeType(typeOf(s)))`
### i) encodeType
- `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"`
- each member is written as `type ‖ " " ‖ name`
- encodings cascade down and are sorted by name
### ii) encodeData
- `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`
- each encoded member is 32-byte long
#### a) atomic
- `boolean` => `uint256`
- `address` => `uint160`
- `uint` => sign-extended `uint256` in big endian order
- `bytes1:31` => `bytes32`
#### b) dynamic
- `bytes` => `keccak256(bytes)`
- `string` => `keccak256(string)`
#### c) referenced
- `array` => `keccak256(encodeData(array))`
- `struct` => `rec(keccak256(hashStruct(struct)))`
## C) Example
### Query
```json
{
"jsonrpc": "2.0",
"method": "account_signStructuredData",
"params": [
"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"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",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
],
"id": 1
}
```
### Response
```json
{
"id":1,
"jsonrpc": "2.0",
"result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
}
```

57
signer/core/apiv2.go Normal file
View file

@ -0,0 +1,57 @@
package core
import (
"context"
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
//type TypedData struct {
//Types map[string] interface{} `json:"types"`
//PrimaryType *big.Int `json:"primaryType"`
//Domain EIP712Domain `json:"domain"`
//Message map[string] interface{} `json:"message"`
//}
//type EIP712Domain struct {
// Name string `json:"name"`
// Version string `json:"version"`
// ChainId big.Int `json:"chainId"`
// VerifyingContract common.Address `json:"verifyingContract"`
// Salt hexutil.Bytes `json:"salt"`
//}
// TypedData represents a request to create a new filter.
// Same as ethereum.FilterQuery but with UnmarshalJSON() method.
type TypedData ethereum.TypedData
// Typed data according to EIP712
//
// If the format "\x19\x46" ‖ domainSeparator ‖ hashStruct(message)` is not respected,
// an error is returned
func (api *SignerAPI) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) {
fmt.Println("data", data)
fmt.Println("data.Hash", data.Hash)
return common.Hex2Bytes("0xdeadbeef"), nil
}
// UnmarshalJSON sets *args fields with given data.
func (args *TypedData) UnmarshalJSON(data []byte) error {
type input struct {
Hash *common.Hash `json:"hash"`
}
var raw input
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
if raw.Hash != nil {
args.Hash = raw.Hash
}
return nil
}

View file

@ -18,7 +18,6 @@ package core
import (
"context"
"encoding/json"
"github.com/ethereum/go-ethereum/accounts"
@ -71,6 +70,14 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com
return b, e
}
func (l *AuditLogger) SignStructuredData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) {
l.log.Info("SignStructuredData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", data)
b, e := l.api.SignStructuredData(ctx, addr, TypedData{})
l.log.Info("SignStructuredData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e
}
func (l *AuditLogger) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) {
l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType)