mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 02:42:27 +00:00
Implementing UnmarshalJSON() for TypedData
This commit is contained in:
parent
a16c250409
commit
e440c25a2b
7 changed files with 230 additions and 12 deletions
11
cmd/clef/audit.log
Normal file
11
cmd/clef/audit.log
Normal 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]}"
|
||||||
|
|
@ -327,9 +327,10 @@ func initialize(c *cli.Context) error {
|
||||||
// If using the stdioui, we can't do the 'confirm'-flow
|
// If using the stdioui, we can't do the 'confirm'-flow
|
||||||
fmt.Fprintf(logOutput, legalWarning)
|
fmt.Fprintf(logOutput, legalWarning)
|
||||||
} else {
|
} else {
|
||||||
if !confirm(legalWarning) {
|
// Temporarily disabled while in development
|
||||||
return fmt.Errorf("aborted by user")
|
//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))))
|
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
|
var password string
|
||||||
// If ui is not nil, get the password from ui.
|
// If ui is not nil, get the password from ui.
|
||||||
if ui != nil {
|
if ui != nil {
|
||||||
resp, err := ui.OnInputRequired(core.UserInputRequest{
|
// Temporarily disabled while in development
|
||||||
Title: "Master Password",
|
//resp, err := ui.OnInputRequired(core.UserInputRequest{
|
||||||
Prompt: "Please enter the password to decrypt the master seed",
|
// Title: "Master Password",
|
||||||
IsPassword: true})
|
// Prompt: "Please enter the password to decrypt the master seed",
|
||||||
if err != nil {
|
// IsPassword: true})
|
||||||
return nil, err
|
//if err != nil {
|
||||||
}
|
// return nil, err
|
||||||
password = resp.Text
|
//}
|
||||||
|
//password = resp.Text
|
||||||
} else {
|
} else {
|
||||||
password = getPassPhrase("Decrypt master seed of clef", false)
|
password = getPassPhrase("Decrypt master seed of clef", false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -209,3 +209,8 @@ type GasEstimator interface {
|
||||||
type PendingStateEventer interface {
|
type PendingStateEventer interface {
|
||||||
SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error)
|
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"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ type ExternalAPI interface {
|
||||||
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
|
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
|
||||||
// SignData - request to sign the given data (plus prefix)
|
// SignData - request to sign the given data (plus prefix)
|
||||||
SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
|
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 - recover public key from given message and signature
|
||||||
EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
|
EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
|
||||||
// Export - request to export an account
|
// Export - request to export an account
|
||||||
|
|
|
||||||
134
signer/core/api_layout.md
Normal file
134
signer/core/api_layout.md
Normal 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
57
signer/core/apiv2.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,6 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
|
@ -71,6 +70,14 @@ func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr com
|
||||||
return b, e
|
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) {
|
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(),
|
l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(),
|
||||||
"data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType)
|
"data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig), "content-type", contentType)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue