diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log new file mode 100644 index 0000000000..66302cac97 --- /dev/null +++ b/cmd/clef/audit.log @@ -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]}" diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 2d0805648b..6c1290b9cd 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -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) } diff --git a/interfaces.go b/interfaces.go index 1ff31f96b6..840e916aaf 100644 --- a/interfaces.go +++ b/interfaces.go @@ -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"` +} diff --git a/signer/core/api.go b/signer/core/api.go index 68fe9b7837..6171331313 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -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 diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md new file mode 100644 index 0000000000..bcb8c4cc5b --- /dev/null +++ b/signer/core/api_layout.md @@ -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)` +
+
+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))` +
+`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" +} +``` \ No newline at end of file diff --git a/signer/core/apiv2.go b/signer/core/apiv2.go new file mode 100644 index 0000000000..0ef25cb8d2 --- /dev/null +++ b/signer/core/apiv2.go @@ -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 +} \ No newline at end of file diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index ac16b144d0..ed14d5cc64 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -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)