From 7a90dc7fb0d8e9050c838343faff4289b7752fcd Mon Sep 17 00:00:00 2001 From: Paul Berg Date: Sun, 21 Oct 2018 13:58:00 +0100 Subject: [PATCH] Drafted recursive encodeData --- cmd/clef/audit.log | 84 +++++ signer/core/api.go | 179 ----------- signer/core/api_layout.md | 24 +- signer/core/signed_data.go | 637 ++++++++++++++++++++++++++++--------- 4 files changed, 595 insertions(+), 329 deletions(-) diff --git a/cmd/clef/audit.log b/cmd/clef/audit.log index 9b0ef72b85..667d0443b4 100644 --- a/cmd/clef/audit.log +++ b/cmd/clef/audit.log @@ -62,3 +62,87 @@ t=2018-10-15T03:12:04+0100 lvl=info msg=SignTypedData api=signer type=response d t=2018-10-15T03:14:48+0100 lvl=info msg=Configured api=signer audit log=audit.log t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:50635\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" t=2018-10-15T03:15:25+0100 lvl=info msg=SignTypedData api=signer type=response data=deadbeef error=nil +t=2018-10-20T15:17:32+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:17:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:22:24+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:24:48+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56168\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:24:54+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:25:28+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:27:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:27:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:28:02+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56262\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:28:15+0100 lvl=info msg=SignTypedData api=signer type=response data= error="primary type undefined" +t=2018-10-20T15:29:47+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:29:52+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56281\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[] PrimaryType: Domain:{Name: Version: ChainId: VerifyingContract:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] Salt:0x} Message:map[]}" +t=2018-10-20T15:32:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:32:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56456\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T15:34:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:35:24+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56476\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T15:37:05+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:37:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56561\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[type:string name:name] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T15:38:27+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:38:36+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56583\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T15:40:49+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:40:54+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56604\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T15:41:20+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:41:38+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56681\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T15:49:10+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:49:20+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56804\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[type:string name:name] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T15:51:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:51:31+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56859\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T15:51:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T15:52:23+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:56905\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:05:44+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:05:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58137\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob!]}" +t=2018-10-20T16:06:22+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:06:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58154\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[type:string name:name] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T16:06:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:06:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58213\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[type:address name:verifyingContract]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:06:55+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:07:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58301\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[type:string name:name] map[name:wallet type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T16:10:03+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:10:05+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58446\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[type:string name:contents]] EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:10:39+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:10:43+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58497\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T16:12:31+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:12:37+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:12:55+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58538\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:14:08+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:14:11+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58563\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[type:string name:version] map[name:chainId type:uint256] map[type:address name:verifyingContract]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" +t=2018-10-20T16:17:07+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:17:13+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58619\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[type:string name:name] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:18:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:19:01+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:20:03+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58718\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T16:24:34+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:25:59+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58919\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:30:18+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:30:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:58983\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T16:30:59+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:31:01+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59036\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T16:31:36+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T16:32:25+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59121\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[type:string name:contents]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 name:Alice]]}" +t=2018-10-20T19:01:23+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:01:51+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59614\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T19:02:26+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:02:41+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:59722\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T19:06:00+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:07:47+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T19:08:16+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60455\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[type:uint256 name:chainId] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB]]}" +t=2018-10-20T19:09:46+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60587\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T19:11:12+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:11:21+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB name:Bob] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T19:11:35+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60710\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[type:Person name:to] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T19:11:51+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:11:53+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60768\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T19:12:11+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:12:30+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60809\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[type:Person name:from] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T19:13:53+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:14:04+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:60898\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]] Person:[map[type:string name:name] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob! from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21]]}" +t=2018-10-20T19:14:32+0100 lvl=info msg=SignTypedData api=signer type=response data=686365d6fa3d2a98d7c67101a8acf295c644a8de80b8ecc89cff276355897f6d error=nil +t=2018-10-20T19:36:35+0100 lvl=info msg=Configured api=signer audit log=audit.log +t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=request metadata="{\"remote\":\"127.0.0.1:61953\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\",\"User-Agent\":\"PostmanRuntime/7.3.0\",\"Origin\":\"\"}" addr="0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21 [chksum INVALID]" data="{Types:map[Person:[map[name:name type:string] map[name:wallet type:address]] Mail:[map[name:from type:Person] map[name:to type:Person] map[name:contents type:string]] EIP712Domain:[map[name:name type:string] map[name:version type:string] map[name:chainId type:uint256] map[name:verifyingContract type:address]]] PrimaryType:Mail Domain:{Name:Ether Mail Version:1 ChainId:+1 VerifyingContract:[204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204 204] Salt:0x} Message:map[from:map[name:Alice wallet:0x66d76e6a80dc7d46d7ec1b79b15dfa34c3c6ef21] to:map[name:Bob wallet:0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB] contents:Hello, Bob!]}" +t=2018-10-20T19:36:40+0100 lvl=info msg=SignTypedData api=signer type=response data=7951623617a41fec181fafc3555c0a494d01dce0408b6596964f5f50acba239e error=nil diff --git a/signer/core/api.go b/signer/core/api.go index 691372f249..136b3682a4 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -21,11 +21,8 @@ import ( "encoding/json" "errors" "fmt" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/sha3" "io/ioutil" "math/big" - "mime" "reflect" "github.com/ethereum/go-ethereum/accounts" @@ -33,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -542,181 +538,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth } -// SignData signs the hash of the provided data, but does so differently -// depending on the content-type specified. -// -// Depending on the content-type, different types of validations will occur. -// -// Note, the produced signature conforms to the secp256k1 curve R, S and V values, -// where the V value will be 27 or 28 for legacy reasons. -func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { - - var req, err = api.determineSignatureFormat(contentType, data) - if err != nil { - return nil, err - } - req.Address = addr - req.Meta = MetadataFromContext(ctx) - - // We make the request prior to looking up if we actually have the account, to prevent - // account-enumeration via the API - res, err := api.UI.ApproveSignData(req) - if err != nil { - return nil, err - } - if !res.Approved { - return nil, ErrRequestDenied - } - // Look up the wallet containing the requested signer - account := accounts.Account{Address: addr.Address()} - wallet, err := api.am.Find(account) - if err != nil { - return nil, err - } - // Sign the data with the wallet - signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) - if err != nil { - api.UI.ShowError(err.Error()) - return nil, err - } - signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper - return signature, nil -} - -// Determines which signature method should be used based upon the mime type -func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { - var req *SignDataRequest - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return nil, err - } - switch mediaType { - case TextValidator.Mime: - // Data with an intended validator - - sighash, msg := signTextWithValidator(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case TextPlain.Mime: - // Sign calculates an Ethereum ECDSA signature for: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - - // In the cases where it matters ensure that the charset is handled. The charset - // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType - // charset, ok := params["charset"] - // As it is now, we accept any charset and just treat it as 'raw'. - - sighash, msg := signTextPlain(data) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - case ApplicationClique.Mime: - // Clique is the Ethereum PoA standard - header := &types.Header{} - if err := rlp.DecodeBytes(data, header); err != nil { - return nil, err - } - sighash, err := signCliqueHeader(header) - if err != nil { - return nil, err - } - msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) - req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} - default: - return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) - } - return req, nil - -} - -// signTextPlain is a helper function that calculates a hash for the given message that can be -// safely used to calculate a signature from. -// -// The hash is calculated as -// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). -// -// This gives context to the signed message and prevents signing of transactions. -func signTextPlain(data []byte) ([]byte, string) { - msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) - return crypto.Keccak256([]byte(msg)), msg -} - -// signTextWithValidator signs the given message which can be further recovered -// with the given validator. -func signTextWithValidator(data []byte) ([]byte, string) { - msg := "TODO" - return crypto.Keccak256([]byte(msg)), msg -} - -// signCliqueHeader returns the hash which is used as input for the proof-of-authority -// signing. It is the hash of the entire header apart from the 65 byte signature -// contained at the end of the extra data. -// -// The method requires the extra data to be at least 65 bytes -- the original implementation -// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic -// and simply return an error instead -func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { - hash := common.Hash{} - if len(header.Extra) < 65 { - return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) - } - hasher := sha3.NewKeccak256() - rlp.Encode(hasher, []interface{}{ - header.ParentHash, - header.UncleHash, - header.Coinbase, - header.Root, - header.TxHash, - header.ReceiptHash, - header.Bloom, - header.Difficulty, - header.Number, - header.GasLimit, - header.GasUsed, - header.Time, - header.Extra[:len(header.Extra)-65], - header.MixDigest, - header.Nonce, - }) - hasher.Sum(hash[:0]) - return hash.Bytes(), nil -} - -// Determines the content type and then recovers the address associated with the given sig -func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { - mediaType, _, err := mime.ParseMediaType(contentType) - if err != nil { - return common.Address{}, err - } - switch mediaType { - case TextPlain.Mime: - // Returns the address for the Account that was used to create the signature. - // - // Note, this function is compatible with eth_sign and personal_sign. As such it recovers - // the address of: - // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") - // addr = ecrecover(hash, signature) - // - // Note, the signature must conform to the secp256k1 curve R, S and V values, where - // the V value must be be 27 or 28 for legacy reasons. - // - // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover - - if len(sig) != 65 { - return common.Address{}, fmt.Errorf("signature must be 65 bytes long") - } - if sig[64] != 27 && sig[64] != 28 { - return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") - } - sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 - hash, _ := signTextPlain(data) - rpk, err := crypto.SigToPub(hash, sig) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*rpk), nil - default: - return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) - } -} - // Export returns encrypted private key associated with the given address in web3 keystore format. func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) diff --git a/signer/core/api_layout.md b/signer/core/api_layout.md index 1a251fe9ee..3aae11fd07 100644 --- a/signer/core/api_layout.md +++ b/signer/core/api_layout.md @@ -1,7 +1,7 @@ # Specs -`encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)` +`encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01EthereumSignedMessage\n" ‖ domainSeparator ‖ hashStruct(message)` - data adheres to 𝕊, a structure defined in the rigorous eip-712 -- `\x00` is needed to comply with EIP-191 +- `\x01` is needed to comply with EIP-191 - `domainSeparator` and `hashStruct` are defined below ## A) domainSeparator @@ -34,9 +34,9 @@ Example: `Mail(Person from,Person to,string contents)Person(string name,address #### a) atomic - - `boolean` => `uint256` + - `bool` => `uint256` - `address` => `uint160` - - `uint` => sign-extended `uint256` in big endian order + - `int8:int256` and `uint8:uint256` => sign-extended `uint256` in big endian order - `bytes1:31` => `bytes32` #### b) dynamic @@ -49,7 +49,21 @@ Example: `Mail(Person from,Person to,string contents)Person(string name,address - `array` => `keccak256(encodeData(array))` - `struct` => `rec(keccak256(hashStruct(struct)))` -## C) Example +## C) Algo +- hashStruct + - encodeType + - encodeData + - if primitive + - encode + - else + - if array + - encodeData + - else if struct + - hashStruct + - else + - break + +## D) Example ### Query ```json { diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go index 79e49989ac..eef2c89b3c 100644 --- a/signer/core/signed_data.go +++ b/signer/core/signed_data.go @@ -4,14 +4,18 @@ import ( "bytes" "context" "encoding/hex" + "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" "math/big" - "math/rand" + "mime" "reflect" "sort" "strings" - "time" "unicode" "github.com/PaulRBerg/basics/helpers" @@ -22,14 +26,16 @@ import ( ) type TypedData struct { - Types map[string]EIP712Type `json:"types"` - PrimaryType string `json:"primaryType"` - Domain EIP712Domain `json:"domain"` - Message EIP712Message `json:"message"` + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` } type EIP712Type []map[string]string +type EIP712Types map[string]EIP712Type + type EIP712TypePriority struct { Type string Value uint @@ -38,79 +44,255 @@ type EIP712TypePriority struct { type EIP712Data = map[string]interface{} 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"` + Name string `json:"name"` + Version string `json:"version"` + ChainId *big.Int `json:"chainId"` + VerifyingContract common.Address `json:"verifyingContract"` + Salt hexutil.Bytes `json:"salt"` } -type EIP712Message map[string]interface{} +const ( + TypeArray = "array" + TypeAddress = "address" + TypeBool = "bool" + TypeBytes = "bytes" + TypeInt = "int" + TypeString = "string" +) + +// Sign receives a request and produces a signature + +// Note, the produced signature conforms to the secp256k1 curve R, S and V values, +// where the V value will be 27 or 28 for legacy reasons. +func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, req *SignDataRequest) ([]byte, error) { + req.Address = addr + req.Meta = MetadataFromContext(ctx) + + // We make the request prior to looking up if we actually have the account, to prevent + // account-enumeration via the API + res, err := api.UI.ApproveSignData(req) + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + account := accounts.Account{Address: addr.Address()} + wallet, err := api.am.Find(account) + if err != nil { + return nil, err + } + // Sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, req.Hash) + if err != nil { + return nil, err + } + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil +} + +// SignData signs the hash of the provided data, but does so differently +// depending on the content-type specified. +// +// Different types of validation occur. +func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { + return nil, err + } + + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + + return signature, nil +} + +// Determines which signature method should be used based upon the mime type +func (api *SignerAPI) determineSignatureFormat(contentType string, data hexutil.Bytes) (*SignDataRequest, error) { + var req *SignDataRequest + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return nil, err + } + + switch mediaType { + case TextValidator.Mime: + // Data with an intended validator + sighash, msg := signTextWithValidator(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + case TextPlain.Mime: + // Sign calculates an Ethereum ECDSA signature for: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + + // In the cases where it matters ensure that the charset is handled. The charset + // resides in the 'params' returned as the second returnvalue from mime.ParseMediaType + // charset, ok := params["charset"] + // As it is now, we accept any charset and just treat it as 'raw'. + sighash, msg := signTextPlain(data) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + case ApplicationClique.Mime: + // Clique is the Ethereum PoA standard + header := &types.Header{} + if err := rlp.DecodeBytes(data, header); err != nil { + return nil, err + } + sighash, err := signCliqueHeader(header) + if err != nil { + return nil, err + } + msg := fmt.Sprintf("Clique block %d [0x%x]", header.Number, header.Hash()) + req = &SignDataRequest{Rawdata: data, Message: msg, Hash: sighash, ContentType: mediaType} + break + default: + return nil, fmt.Errorf("content type '%s' not implemented for signing", contentType) + } + return req, nil + +} + +// signTextPlain is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19${byteVersion}Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func signTextPlain(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message:\n%d%s", TextPlain.ByteVersion, len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +// signTextWithValidator signs the given message which can be further recovered +// with the given validator. +func signTextWithValidator(data []byte) ([]byte, string) { + msg := "TODO" + return crypto.Keccak256([]byte(msg)), msg +} + +// signCliqueHeader returns the hash which is used as input for the proof-of-authority +// signing. It is the hash of the entire header apart from the 65 byte signature +// contained at the end of the extra data. +// +// The method requires the extra data to be at least 65 bytes -- the original implementation +// in clique.go panics if this is the case, thus it's been reimplemented here to avoid the panic +// and simply return an error instead +func signCliqueHeader(header *types.Header) (hexutil.Bytes, error) { + hash := common.Hash{} + if len(header.Extra) < 65 { + return hash.Bytes(), fmt.Errorf("clique header extradata too short, %d < 65", len(header.Extra)) + } + hasher := sha3.NewKeccak256() + rlp.Encode(hasher, []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.Extra[:len(header.Extra)-65], + header.MixDigest, + header.Nonce, + }) + hasher.Sum(hash[:0]) + return hash.Bytes(), nil +} // SignTypedData signs EIP712 conformant typed data // hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}") -func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) { - if err := data.Domain.IsValid(); err != nil { +func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) { + domainTypes := EIP712Types{ + "EIP712Domain": typedData.Types["EIP712Domain"], + } + domainSeparatorBytes := hashStruct(domainTypes, typedData.Domain.Map(), "EIP712Domain", 0) + domainSeparator := common.Bytes2Hex(domainSeparatorBytes) + //if err != nil { + // return nil, err + //} + delete(typedData.Types, "EIP712Domain") + typedDataHashBytes := hashStruct(typedData.Types, typedData.Message, typedData.PrimaryType, 0) + typedDataHash := common.Bytes2Hex(typedDataHashBytes) + //if err != nil { + // return nil, err + //} + + fmt.Println("domainSeparator", domainSeparator) + fmt.Println("typedDataHash", typedDataHash) + + var buffer bytes.Buffer + buffer.WriteString("\x19\x01") + buffer.WriteString(domainSeparator) + buffer.WriteString(typedDataHash) + + sighash := crypto.Keccak256(buffer.Bytes()) + msg := fmt.Sprintf("\x19\\x%xEthereum Signed Message\n:%s%s", DataTyped.ByteVersion, domainSeparator, typedDataHash) + //req := &SignDataRequest{Rawdata: typedData, Message: msg, Hash: sighash, ContentType: DataTyped.Mime} + + var req, err = api.determineSignatureFormat(contentType, data) + if err != nil { return nil, err } - if data.PrimaryType == "" { - return nil, errors.New("primary type undefined") + + signature, err := api.Sign(ctx, addr, req) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err } - domainTypes := map[string]EIP712Type{ - "EIP712Domain": data.Types["EIP712Domain"], - } - domainSeparator := hashStruct(domainTypes, data.PrimaryType, data.Domain.Values(), 0) - //if err != nil { - // return nil, err - //} - delete(data.Types, "EIP712Domain") - typedDataHash := hashStruct(data.Types, data.PrimaryType, data.Message, 0) - //if err != nil { - // return nil, err - //} + return signature, nil - fmt.Println("domainSeparator", domainSeparator.String()) - fmt.Println("typedDataHash", typedDataHash.String()) - return common.FromHex("0xdeadbeef"), nil + return crypto.Keccak256(buffer.Bytes()), nil } // hashStruct generates the following encoding for the given domain and message: // `encode(domainSeparator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)` -func hashStruct(types map[string]EIP712Type, key string, data EIP712Data, depth int) common.Hash { +func hashStruct(_types EIP712Types, data EIP712Data, dataType string, depth int) []byte { helpers.PrintJson("hashStruct", map[string]interface{}{ "depth": depth, }) - typeEncoding := encodeType(types) - typeHash := hex.EncodeToString(crypto.Keccak256([]byte(typeEncoding))) + typeEncoding := encodeType(_types) + typeHash := hex.EncodeToString(crypto.Keccak256(typeEncoding)) - dataEncoding := encodeData(types, key, data, depth) - dataHash := hex.EncodeToString(crypto.Keccak256([]byte(dataEncoding))) + dataEncoding := encodeData(_types, data, dataType, depth) + dataHash := hex.EncodeToString(crypto.Keccak256(dataEncoding)) var buffer bytes.Buffer buffer.WriteString(typeHash) buffer.WriteString(dataHash) - hash := common.BytesToHash(crypto.Keccak256(buffer.Bytes())) + encoding := crypto.Keccak256(buffer.Bytes()) if depth == 0 { - fmt.Printf("typeEncoding %s\n", typeEncoding) - fmt.Printf("dataEncoding %s\n", dataEncoding) + fmt.Printf("typeEncoding %s\n", common.Bytes2Hex(typeEncoding)) + fmt.Printf("dataEncoding %s\n", common.Bytes2Hex(dataEncoding)) } - return hash + + return encoding } // encodeType generates the followign encoding: // `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"` // // each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name -func encodeType(types map[string]EIP712Type) string { - helpers.PrintJson("hashStruct", map[string]interface{}{ - "types": types, +func encodeType(_types EIP712Types) []byte { + helpers.PrintJson("encodeType", map[string]interface{}{ + "types": _types, }) + //fmt.Printf("encodeType: types %v\n\n", types) var priorities = make(map[string]uint) - for key := range types { + for key := range _types { priorities[key] = 0 } @@ -119,7 +301,7 @@ func encodeType(types map[string]EIP712Type) string { priorities[typeVal]++ // Importantly, we also have to check for parent types to increment them too - for _, typeObj := range types[typeVal] { + for _, typeObj := range _types[typeVal] { _typeVal := typeObj["type"] firstChar := []rune(_typeVal)[0] @@ -139,32 +321,22 @@ func encodeType(types map[string]EIP712Type) string { return false } - for typeKey, typeArr := range types { + for typeKey, typeArr := range _types { var typeValArr []string for _, typeObj := range typeArr { typeVal := typeObj["type"] - if typeKey == typeVal { - panic(fmt.Errorf("type %s cannot reference itself", typeVal)) - } + //if typeKey == typeVal { + // panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + //} firstChar := []rune(typeVal)[0] if unicode.IsUpper(firstChar) { - if types[typeVal] != nil { + if _types[typeVal] != nil { if !visited(typeValArr, typeVal) { typeValArr = append(typeValArr, typeVal) update(typeKey, typeVal) } - } else { - panic(fmt.Errorf("referenced type %s is undefined", typeVal)) - } - } else { - if !isStandardType(typeVal) { - if types[typeVal] != nil { - panic(fmt.Errorf("Custom type %s must be capitalized", typeVal)) - } else { - panic(fmt.Errorf("Unknown type %s", typeVal)) - } } } } @@ -176,7 +348,7 @@ func encodeType(types map[string]EIP712Type) string { var buffer bytes.Buffer for _, priority := range sortedPriorities { typeKey := priority.Type - typeArr := types[typeKey] + typeArr := _types[typeKey] buffer.WriteString(typeKey) buffer.WriteString("(") @@ -192,79 +364,7 @@ func encodeType(types map[string]EIP712Type) string { buffer.WriteString(")") } - return buffer.String() -} - -// encodeData generates the following encoding: -// `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)` -// -// each encoded member is 32-byte long -func encodeData(types map[string]EIP712Type, key string, val interface{}, depth int) string { - helpers.PrintJson("hashStruct", map[string]interface{}{ - "key": key, - "val": val, - "depth": depth, - }) - - var buffer bytes.Buffer - - switch val.(type) { - case EIP712Data: - for mapKey, mapVal := range val.(EIP712Data) { - if reflect.TypeOf(mapVal) == reflect.TypeOf(EIP712Data{}) { - hash := hashStruct(types, mapKey, mapVal.(EIP712Data), depth+1) - buffer.WriteString(hash.String()) - } else { - str := encodeData(types, mapKey, mapVal, depth+1) - buffer.WriteString(str) - } - } - break - - case bool: - boolVal, _ := val.(bool) - var int64Val int64 - if boolVal { - int64Val = 1 - } - encodedVal := abi.U256(big.NewInt(int64Val)) - fmt.Printf("bool encoded value:", encodedVal) - buffer.Write(encodedVal) - break - - case string: - bytesVal := common.FromHex(val.(string)) - hash := common.BytesToHash(crypto.Keccak256(bytesVal)) - buffer.WriteString(hash.String()) - break - - default: - arr := [...]string{"(a)", "(b)", "(c)"} - rand.Seed(time.Now().UnixNano()) - buffer.WriteString(arr[rand.Intn(3)]) - break - } - - return buffer.String() -} - -// isStandardType checks if the given type is a EIP712 conformant type -func isStandardType(typeStr string) bool { - standardTypes := []string{ - "array", - "address", - "boolean", - "bytes", - "string", - "struct", - "uint", - } - for _, val := range standardTypes { - if strings.HasPrefix(typeStr, val) { - return true - } - } - return false + return buffer.Bytes() } // sortByPriorityAndName is a helper function to sort types by priority and name. Priority is calculated b @@ -283,14 +383,210 @@ func sortByPriorityAndName(input map[string]uint) []EIP712TypePriority { return priorities[i].Value > priorities[j].Value }) - for _, priority := range priorities { - fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) - } - fmt.Printf("\n") + //for _, priority := range priorities { + // fmt.Printf("%s, Value %d\n", priority.Type, priority.Value) + //} + //fmt.Printf("\n") return priorities } +// encodeData generates the following encoding: +// `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)` +// +// each encoded member is 32-byte long +func encodeData(_types EIP712Types, data interface{}, dataType string, depth int) []byte { + helpers.PrintJson("encodeData", map[string]interface{}{ + "dataType": dataType, + "data": data, + "depth": depth, + }) + + var buffer bytes.Buffer + + // TODO regex + // handle arrays + if strings.Contains(dataType, "[]") { + arrayVal := data.([]interface{}) + dataType := "TODO" + + var arrayBuffer bytes.Buffer + for obj := range arrayVal { + objEncoding := encodeData(_types, obj, dataType, depth+1) + arrayBuffer.Write(objEncoding) + } + + encoding := arrayBuffer.Bytes() + buffer.Write(encoding) + return buffer.Bytes() + } + + // handle maps + firstChar := []rune(dataType)[0] + if unicode.IsUpper(firstChar) { + for mapKey, mapVal := range data.(EIP712Data) { + nextDataType := findNextDataType(_types, dataType, mapKey) + if reflect.TypeOf(mapVal) == reflect.TypeOf(EIP712Data{}) { + data := mapVal.(map[string]interface{}) + encoding := hashStruct(_types, data, nextDataType, depth+1) + buffer.Write(encoding) + } else { + encoding := encodeData(_types, mapVal, nextDataType, depth+1) + buffer.Write(encoding) + } + } + return buffer.Bytes() + } + + // TODO regex + // handle bytes + if strings.Contains(dataType, TypeBytes) { + bytesVal := data.([]byte) + encoding := crypto.Keccak256(bytesVal) + buffer.Write(encoding) + } + + // TODO regex + // handle ints + if strings.Contains(dataType, TypeInt) { + encoding := abi.U256(data.(*big.Int)) // not sure if this is big endian order, but it's definitey sign extended to 256 bit because of using the U256 function + buffer.Write(encoding) + return buffer.Bytes() + } + + // handle what's left + switch dataType { + case TypeAddress: + addressVal, _ := data.(common.Address) + encoding := addressVal.Bytes() // hopefully this means uint160 encoding? + buffer.Write(encoding) + break + case TypeBool: + boolVal, _ := data.(bool) + var int64Val int64 + if boolVal { + int64Val = 1 + } + encoding := abi.U256(big.NewInt(int64Val)) + buffer.Write(encoding) + break + case TypeString: + bytesVal := common.FromHex(data.(string)) + encoding := crypto.Keccak256(bytesVal) + buffer.Write(encoding) + break + default: + break + } + + return buffer.Bytes() +} + +// findNextDataType +// blah blah +func findNextDataType(_types EIP712Types, mapType string, mapKey string) string { + eip712type := _types[mapType] + + for _, mapObj := range eip712type { + if mapObj["name"] == mapKey { + return mapObj["type"] + } + } + + return "" +} + +// UnmarshalJSON validates the input data +func (typedData *TypedData) UnmarshalJSON(data []byte) error { + type input struct { + Types EIP712Types `json:"types"` + PrimaryType string `json:"primaryType"` + Domain EIP712Domain `json:"domain"` + Message EIP712Data `json:"message"` + } + + var raw input + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + if raw.Types == nil { + return errors.New("types are undefined") + } + if err := raw.Types.IsValid(); err != nil { + return err + } + typedData.Types = raw.Types + + if raw.Types["EIP712Domain"] == nil { + return errors.New("domain types are undefined") + } + if err := raw.Domain.IsValid(); err != nil { + return err + } + typedData.Domain = raw.Domain + + if len(raw.PrimaryType) == 0 { + return errors.New("primary type is undefined") + } + typedData.PrimaryType = raw.PrimaryType + + if raw.Message == nil { + return errors.New("message is undefined") + } + typedData.Message = raw.Message + + return nil +} + +// isStandardType checks if the given type is a EIP712 conformant type +func isStandardTypeStr(typeStr string) bool { + standardTypes := []string{ + "array", + "address", + "boolean", + "bytes", + "string", + "struct", + "uint", + } + for _, val := range standardTypes { + if strings.HasPrefix(typeStr, val) { + return true + } + } + return false +} + + +// IsValid checks if the given types object is conformant to the specs +func (types *EIP712Types) IsValid() error { + for typeKey, typeArr := range (*types) { + for _, typeObj := range typeArr { + typeVal := typeObj["type"] + if typeKey == typeVal { + panic(fmt.Errorf("type %s cannot reference itself", typeVal)) + } + + firstChar := []rune(typeVal)[0] + if unicode.IsUpper(firstChar) { + if (*types)[typeVal] == nil { + return fmt.Errorf("referenced type %s is undefined", typeVal) + } + } else { + if !isStandardTypeStr(typeVal) { + if (*types)[typeVal] != nil { + return fmt.Errorf("custom type %s must be capitalized", typeVal) + } else { + return fmt.Errorf("unknown type %s", typeVal) + } + } + } + } + } + return nil +} + // IsValid checks if the given domain is valid, i.e. contains at least // the minimum viable keys and values func (domain *EIP712Domain) IsValid() error { @@ -307,12 +603,63 @@ func (domain *EIP712Domain) IsValid() error { // Values is a helper function to return the values of a domain as a map // with arbitrary values -func (domain *EIP712Domain) Values() map[string]interface{} { - return map[string]interface{}{ - "name": domain.Name, - "version": domain.Version, - "chainId": domain.Name, - "verifyingContract": domain.VerifyingContract, - "salt": domain.Salt, +func (domain *EIP712Domain) Map() EIP712Data { + dataMap := EIP712Data{ + "chainId": domain.ChainId, } + + if len(domain.Name) > 0 { + dataMap["name"] = domain.Name + } + + if len(domain.Version) > 0 { + dataMap["version"] = domain.Version + } + + if len(domain.VerifyingContract) > 0 { + dataMap["verifyingContract"] = domain.VerifyingContract + } + + if len(domain.Salt) > 0 { + dataMap["salt"] = domain.Salt + } + return dataMap } + +// Determines the content type and then recovers the address associated with the given sig +func (api *SignerAPI) EcRecover(ctx context.Context, contentType string, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) { + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return common.Address{}, err + } + switch mediaType { + case TextPlain.Mime: + // Returns the address for the Account that was used to create the signature. + // + // Note, this function is compatible with eth_sign and personal_sign. As such it recovers + // the address of: + // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}") + // addr = ecrecover(hash, signature) + // + // Note, the signature must conform to the secp256k1 curve R, S and V values, where + // the V value must be be 27 or 28 for legacy reasons. + // + // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover + + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := signTextPlain(data) + rpk, err := crypto.SigToPub(hash, sig) + if err != nil { + return common.Address{}, err + } + return crypto.PubkeyToAddress(*rpk), nil + default: + return common.Address{}, fmt.Errorf("content type '%s' not implemented for ecRecover", contentType) + } +} \ No newline at end of file