Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
619a3e70
Unverified
Commit
619a3e70
authored
Dec 05, 2021
by
Martin Holst Swende
Committed by
GitHub
Dec 05, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
signer/core: move EIP-712 types to package apitypes (#24029)
Fixes #23972
parent
93f196c4
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
776 additions
and
771 deletions
+776
-771
main.go
cmd/clef/main.go
+2
-2
api.go
signer/core/api.go
+2
-2
signed_data_internal_test.go
signer/core/apitypes/signed_data_internal_test.go
+1
-1
types.go
signer/core/apitypes/types.go
+718
-0
auditlog.go
signer/core/auditlog.go
+1
-1
gnosis_safe.go
signer/core/gnosis_safe.go
+7
-7
signed_data.go
signer/core/signed_data.go
+19
-733
signed_data_test.go
signer/core/signed_data_test.go
+25
-24
rules_test.go
signer/rules/rules_test.go
+1
-1
No files found.
cmd/clef/main.go
View file @
619a3e70
...
@@ -898,7 +898,7 @@ func testExternalUI(api *core.SignerAPI) {
...
@@ -898,7 +898,7 @@ func testExternalUI(api *core.SignerAPI) {
addr
,
_
:=
common
.
NewMixedcaseAddressFromString
(
"0x0011223344556677889900112233445566778899"
)
addr
,
_
:=
common
.
NewMixedcaseAddressFromString
(
"0x0011223344556677889900112233445566778899"
)
data
:=
`{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}`
data
:=
`{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}`
//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
var
typedData
core
.
TypedData
var
typedData
apitypes
.
TypedData
json
.
Unmarshal
([]
byte
(
data
),
&
typedData
)
json
.
Unmarshal
([]
byte
(
data
),
&
typedData
)
_
,
err
:=
api
.
SignTypedData
(
ctx
,
*
addr
,
typedData
)
_
,
err
:=
api
.
SignTypedData
(
ctx
,
*
addr
,
typedData
)
expectApprove
(
"sign 712 typed data"
,
err
)
expectApprove
(
"sign 712 typed data"
,
err
)
...
@@ -1025,7 +1025,7 @@ func GenDoc(ctx *cli.Context) {
...
@@ -1025,7 +1025,7 @@ func GenDoc(ctx *cli.Context) {
"of the work in canonicalizing and making sense of the data, and it's up to the UI to present"
+
"of the work in canonicalizing and making sense of the data, and it's up to the UI to present"
+
"the user with the contents of the `message`"
"the user with the contents of the `message`"
sighash
,
msg
:=
accounts
.
TextAndHash
([]
byte
(
"hello world"
))
sighash
,
msg
:=
accounts
.
TextAndHash
([]
byte
(
"hello world"
))
messages
:=
[]
*
core
.
NameValueType
{{
Name
:
"message"
,
Value
:
msg
,
Typ
:
accounts
.
MimetypeTextPlain
}}
messages
:=
[]
*
apitypes
.
NameValueType
{{
Name
:
"message"
,
Value
:
msg
,
Typ
:
accounts
.
MimetypeTextPlain
}}
add
(
"SignDataRequest"
,
desc
,
&
core
.
SignDataRequest
{
add
(
"SignDataRequest"
,
desc
,
&
core
.
SignDataRequest
{
Address
:
common
.
NewMixedcaseAddress
(
a
),
Address
:
common
.
NewMixedcaseAddress
(
a
),
...
...
signer/core/api.go
View file @
619a3e70
...
@@ -57,7 +57,7 @@ type ExternalAPI interface {
...
@@ -57,7 +57,7 @@ type ExternalAPI interface {
// 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
interface
{})
(
hexutil
.
Bytes
,
error
)
SignData
(
ctx
context
.
Context
,
contentType
string
,
addr
common
.
MixedcaseAddress
,
data
interface
{})
(
hexutil
.
Bytes
,
error
)
// SignTypedData - request to sign the given structured data (plus prefix)
// SignTypedData - request to sign the given structured data (plus prefix)
SignTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
data
TypedData
)
(
hexutil
.
Bytes
,
error
)
SignTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
data
apitypes
.
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
,
data
hexutil
.
Bytes
,
sig
hexutil
.
Bytes
)
(
common
.
Address
,
error
)
EcRecover
(
ctx
context
.
Context
,
data
hexutil
.
Bytes
,
sig
hexutil
.
Bytes
)
(
common
.
Address
,
error
)
// Version info about the APIs
// Version info about the APIs
...
@@ -235,7 +235,7 @@ type (
...
@@ -235,7 +235,7 @@ type (
ContentType
string
`json:"content_type"`
ContentType
string
`json:"content_type"`
Address
common
.
MixedcaseAddress
`json:"address"`
Address
common
.
MixedcaseAddress
`json:"address"`
Rawdata
[]
byte
`json:"raw_data"`
Rawdata
[]
byte
`json:"raw_data"`
Messages
[]
*
NameValueType
`json:"messages"`
Messages
[]
*
apitypes
.
NameValueType
`json:"messages"`
Callinfo
[]
apitypes
.
ValidationInfo
`json:"call_info"`
Callinfo
[]
apitypes
.
ValidationInfo
`json:"call_info"`
Hash
hexutil
.
Bytes
`json:"hash"`
Hash
hexutil
.
Bytes
`json:"hash"`
Meta
Metadata
`json:"meta"`
Meta
Metadata
`json:"meta"`
...
...
signer/core/signed_data_internal_test.go
→
signer/core/
apitypes/
signed_data_internal_test.go
View file @
619a3e70
...
@@ -14,7 +14,7 @@
...
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package
core
package
apitypes
import
(
import
(
"bytes"
"bytes"
...
...
signer/core/apitypes/types.go
View file @
619a3e70
...
@@ -17,16 +17,29 @@
...
@@ -17,16 +17,29 @@
package
apitypes
package
apitypes
import
(
import
(
"bytes"
"encoding/json"
"encoding/json"
"errors"
"fmt"
"fmt"
"math/big"
"math/big"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"strings"
"unicode"
"unicode/utf8"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
)
var
typedDataReferenceTypeRegexp
=
regexp
.
MustCompile
(
`^[A-Z](\w*)(\[\])?$`
)
type
ValidationInfo
struct
{
type
ValidationInfo
struct
{
Typ
string
`json:"type"`
Typ
string
`json:"type"`
Message
string
`json:"message"`
Message
string
`json:"message"`
...
@@ -154,3 +167,708 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
...
@@ -154,3 +167,708 @@ func (args *SendTxArgs) ToTransaction() *types.Transaction {
}
}
return
types
.
NewTx
(
data
)
return
types
.
NewTx
(
data
)
}
}
type
SigFormat
struct
{
Mime
string
ByteVersion
byte
}
var
(
IntendedValidator
=
SigFormat
{
accounts
.
MimetypeDataWithValidator
,
0x00
,
}
DataTyped
=
SigFormat
{
accounts
.
MimetypeTypedData
,
0x01
,
}
ApplicationClique
=
SigFormat
{
accounts
.
MimetypeClique
,
0x02
,
}
TextPlain
=
SigFormat
{
accounts
.
MimetypeTextPlain
,
0x45
,
}
)
type
ValidatorData
struct
{
Address
common
.
Address
Message
hexutil
.
Bytes
}
// TypedData is a type to encapsulate EIP-712 typed messages
type
TypedData
struct
{
Types
Types
`json:"types"`
PrimaryType
string
`json:"primaryType"`
Domain
TypedDataDomain
`json:"domain"`
Message
TypedDataMessage
`json:"message"`
}
// Type is the inner type of an EIP-712 message
type
Type
struct
{
Name
string
`json:"name"`
Type
string
`json:"type"`
}
func
(
t
*
Type
)
isArray
()
bool
{
return
strings
.
HasSuffix
(
t
.
Type
,
"[]"
)
}
// typeName returns the canonical name of the type. If the type is 'Person[]', then
// this method returns 'Person'
func
(
t
*
Type
)
typeName
()
string
{
if
strings
.
HasSuffix
(
t
.
Type
,
"[]"
)
{
return
strings
.
TrimSuffix
(
t
.
Type
,
"[]"
)
}
return
t
.
Type
}
func
(
t
*
Type
)
isReferenceType
()
bool
{
if
len
(
t
.
Type
)
==
0
{
return
false
}
// Reference types must have a leading uppercase character
r
,
_
:=
utf8
.
DecodeRuneInString
(
t
.
Type
)
return
unicode
.
IsUpper
(
r
)
}
type
Types
map
[
string
][]
Type
type
TypePriority
struct
{
Type
string
Value
uint
}
type
TypedDataMessage
=
map
[
string
]
interface
{}
// TypedDataDomain represents the domain part of an EIP-712 message.
type
TypedDataDomain
struct
{
Name
string
`json:"name"`
Version
string
`json:"version"`
ChainId
*
math
.
HexOrDecimal256
`json:"chainId"`
VerifyingContract
string
`json:"verifyingContract"`
Salt
string
`json:"salt"`
}
// HashStruct generates a keccak256 hash of the encoding of the provided data
func
(
typedData
*
TypedData
)
HashStruct
(
primaryType
string
,
data
TypedDataMessage
)
(
hexutil
.
Bytes
,
error
)
{
encodedData
,
err
:=
typedData
.
EncodeData
(
primaryType
,
data
,
1
)
if
err
!=
nil
{
return
nil
,
err
}
return
crypto
.
Keccak256
(
encodedData
),
nil
}
// Dependencies returns an array of custom types ordered by their hierarchical reference tree
func
(
typedData
*
TypedData
)
Dependencies
(
primaryType
string
,
found
[]
string
)
[]
string
{
includes
:=
func
(
arr
[]
string
,
str
string
)
bool
{
for
_
,
obj
:=
range
arr
{
if
obj
==
str
{
return
true
}
}
return
false
}
if
includes
(
found
,
primaryType
)
{
return
found
}
if
typedData
.
Types
[
primaryType
]
==
nil
{
return
found
}
found
=
append
(
found
,
primaryType
)
for
_
,
field
:=
range
typedData
.
Types
[
primaryType
]
{
for
_
,
dep
:=
range
typedData
.
Dependencies
(
field
.
Type
,
found
)
{
if
!
includes
(
found
,
dep
)
{
found
=
append
(
found
,
dep
)
}
}
}
return
found
}
// EncodeType generates the following encoding:
// `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"`
//
// each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name
func
(
typedData
*
TypedData
)
EncodeType
(
primaryType
string
)
hexutil
.
Bytes
{
// Get dependencies primary first, then alphabetical
deps
:=
typedData
.
Dependencies
(
primaryType
,
[]
string
{})
if
len
(
deps
)
>
0
{
slicedDeps
:=
deps
[
1
:
]
sort
.
Strings
(
slicedDeps
)
deps
=
append
([]
string
{
primaryType
},
slicedDeps
...
)
}
// Format as a string with fields
var
buffer
bytes
.
Buffer
for
_
,
dep
:=
range
deps
{
buffer
.
WriteString
(
dep
)
buffer
.
WriteString
(
"("
)
for
_
,
obj
:=
range
typedData
.
Types
[
dep
]
{
buffer
.
WriteString
(
obj
.
Type
)
buffer
.
WriteString
(
" "
)
buffer
.
WriteString
(
obj
.
Name
)
buffer
.
WriteString
(
","
)
}
buffer
.
Truncate
(
buffer
.
Len
()
-
1
)
buffer
.
WriteString
(
")"
)
}
return
buffer
.
Bytes
()
}
// TypeHash creates the keccak256 hash of the data
func
(
typedData
*
TypedData
)
TypeHash
(
primaryType
string
)
hexutil
.
Bytes
{
return
crypto
.
Keccak256
(
typedData
.
EncodeType
(
primaryType
))
}
// EncodeData generates the following encoding:
// `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`
//
// each encoded member is 32-byte long
func
(
typedData
*
TypedData
)
EncodeData
(
primaryType
string
,
data
map
[
string
]
interface
{},
depth
int
)
(
hexutil
.
Bytes
,
error
)
{
if
err
:=
typedData
.
validate
();
err
!=
nil
{
return
nil
,
err
}
buffer
:=
bytes
.
Buffer
{}
// Verify extra data
if
exp
,
got
:=
len
(
typedData
.
Types
[
primaryType
]),
len
(
data
);
exp
<
got
{
return
nil
,
fmt
.
Errorf
(
"there is extra data provided in the message (%d < %d)"
,
exp
,
got
)
}
// Add typehash
buffer
.
Write
(
typedData
.
TypeHash
(
primaryType
))
// Add field contents. Structs and arrays have special handlers.
for
_
,
field
:=
range
typedData
.
Types
[
primaryType
]
{
encType
:=
field
.
Type
encValue
:=
data
[
field
.
Name
]
if
encType
[
len
(
encType
)
-
1
:
]
==
"]"
{
arrayValue
,
ok
:=
encValue
.
([]
interface
{})
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
arrayBuffer
:=
bytes
.
Buffer
{}
parsedType
:=
strings
.
Split
(
encType
,
"["
)[
0
]
for
_
,
item
:=
range
arrayValue
{
if
typedData
.
Types
[
parsedType
]
!=
nil
{
mapValue
,
ok
:=
item
.
(
map
[
string
]
interface
{})
if
!
ok
{
return
nil
,
dataMismatchError
(
parsedType
,
item
)
}
encodedData
,
err
:=
typedData
.
EncodeData
(
parsedType
,
mapValue
,
depth
+
1
)
if
err
!=
nil
{
return
nil
,
err
}
arrayBuffer
.
Write
(
encodedData
)
}
else
{
bytesValue
,
err
:=
typedData
.
EncodePrimitiveValue
(
parsedType
,
item
,
depth
)
if
err
!=
nil
{
return
nil
,
err
}
arrayBuffer
.
Write
(
bytesValue
)
}
}
buffer
.
Write
(
crypto
.
Keccak256
(
arrayBuffer
.
Bytes
()))
}
else
if
typedData
.
Types
[
field
.
Type
]
!=
nil
{
mapValue
,
ok
:=
encValue
.
(
map
[
string
]
interface
{})
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
encodedData
,
err
:=
typedData
.
EncodeData
(
field
.
Type
,
mapValue
,
depth
+
1
)
if
err
!=
nil
{
return
nil
,
err
}
buffer
.
Write
(
crypto
.
Keccak256
(
encodedData
))
}
else
{
byteValue
,
err
:=
typedData
.
EncodePrimitiveValue
(
encType
,
encValue
,
depth
)
if
err
!=
nil
{
return
nil
,
err
}
buffer
.
Write
(
byteValue
)
}
}
return
buffer
.
Bytes
(),
nil
}
// Attempt to parse bytes in different formats: byte array, hex string, hexutil.Bytes.
func
parseBytes
(
encType
interface
{})
([]
byte
,
bool
)
{
switch
v
:=
encType
.
(
type
)
{
case
[]
byte
:
return
v
,
true
case
hexutil
.
Bytes
:
return
v
,
true
case
string
:
bytes
,
err
:=
hexutil
.
Decode
(
v
)
if
err
!=
nil
{
return
nil
,
false
}
return
bytes
,
true
default
:
return
nil
,
false
}
}
func
parseInteger
(
encType
string
,
encValue
interface
{})
(
*
big
.
Int
,
error
)
{
var
(
length
int
signed
=
strings
.
HasPrefix
(
encType
,
"int"
)
b
*
big
.
Int
)
if
encType
==
"int"
||
encType
==
"uint"
{
length
=
256
}
else
{
lengthStr
:=
""
if
strings
.
HasPrefix
(
encType
,
"uint"
)
{
lengthStr
=
strings
.
TrimPrefix
(
encType
,
"uint"
)
}
else
{
lengthStr
=
strings
.
TrimPrefix
(
encType
,
"int"
)
}
atoiSize
,
err
:=
strconv
.
Atoi
(
lengthStr
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid size on integer: %v"
,
lengthStr
)
}
length
=
atoiSize
}
switch
v
:=
encValue
.
(
type
)
{
case
*
math
.
HexOrDecimal256
:
b
=
(
*
big
.
Int
)(
v
)
case
string
:
var
hexIntValue
math
.
HexOrDecimal256
if
err
:=
hexIntValue
.
UnmarshalText
([]
byte
(
v
));
err
!=
nil
{
return
nil
,
err
}
b
=
(
*
big
.
Int
)(
&
hexIntValue
)
case
float64
:
// JSON parses non-strings as float64. Fail if we cannot
// convert it losslessly
if
float64
(
int64
(
v
))
==
v
{
b
=
big
.
NewInt
(
int64
(
v
))
}
else
{
return
nil
,
fmt
.
Errorf
(
"invalid float value %v for type %v"
,
v
,
encType
)
}
}
if
b
==
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid integer value %v/%v for type %v"
,
encValue
,
reflect
.
TypeOf
(
encValue
),
encType
)
}
if
b
.
BitLen
()
>
length
{
return
nil
,
fmt
.
Errorf
(
"integer larger than '%v'"
,
encType
)
}
if
!
signed
&&
b
.
Sign
()
==
-
1
{
return
nil
,
fmt
.
Errorf
(
"invalid negative value for unsigned type %v"
,
encType
)
}
return
b
,
nil
}
// EncodePrimitiveValue deals with the primitive values found
// while searching through the typed data
func
(
typedData
*
TypedData
)
EncodePrimitiveValue
(
encType
string
,
encValue
interface
{},
depth
int
)
([]
byte
,
error
)
{
switch
encType
{
case
"address"
:
stringValue
,
ok
:=
encValue
.
(
string
)
if
!
ok
||
!
common
.
IsHexAddress
(
stringValue
)
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
retval
:=
make
([]
byte
,
32
)
copy
(
retval
[
12
:
],
common
.
HexToAddress
(
stringValue
)
.
Bytes
())
return
retval
,
nil
case
"bool"
:
boolValue
,
ok
:=
encValue
.
(
bool
)
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
if
boolValue
{
return
math
.
PaddedBigBytes
(
common
.
Big1
,
32
),
nil
}
return
math
.
PaddedBigBytes
(
common
.
Big0
,
32
),
nil
case
"string"
:
strVal
,
ok
:=
encValue
.
(
string
)
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
return
crypto
.
Keccak256
([]
byte
(
strVal
)),
nil
case
"bytes"
:
bytesValue
,
ok
:=
parseBytes
(
encValue
)
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
return
crypto
.
Keccak256
(
bytesValue
),
nil
}
if
strings
.
HasPrefix
(
encType
,
"bytes"
)
{
lengthStr
:=
strings
.
TrimPrefix
(
encType
,
"bytes"
)
length
,
err
:=
strconv
.
Atoi
(
lengthStr
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid size on bytes: %v"
,
lengthStr
)
}
if
length
<
0
||
length
>
32
{
return
nil
,
fmt
.
Errorf
(
"invalid size on bytes: %d"
,
length
)
}
if
byteValue
,
ok
:=
parseBytes
(
encValue
);
!
ok
||
len
(
byteValue
)
!=
length
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
else
{
// Right-pad the bits
dst
:=
make
([]
byte
,
32
)
copy
(
dst
,
byteValue
)
return
dst
,
nil
}
}
if
strings
.
HasPrefix
(
encType
,
"int"
)
||
strings
.
HasPrefix
(
encType
,
"uint"
)
{
b
,
err
:=
parseInteger
(
encType
,
encValue
)
if
err
!=
nil
{
return
nil
,
err
}
return
math
.
U256Bytes
(
b
),
nil
}
return
nil
,
fmt
.
Errorf
(
"unrecognized type '%s'"
,
encType
)
}
// dataMismatchError generates an error for a mismatch between
// the provided type and data
func
dataMismatchError
(
encType
string
,
encValue
interface
{})
error
{
return
fmt
.
Errorf
(
"provided data '%v' doesn't match type '%s'"
,
encValue
,
encType
)
}
// validate makes sure the types are sound
func
(
typedData
*
TypedData
)
validate
()
error
{
if
err
:=
typedData
.
Types
.
validate
();
err
!=
nil
{
return
err
}
if
err
:=
typedData
.
Domain
.
validate
();
err
!=
nil
{
return
err
}
return
nil
}
// Map generates a map version of the typed data
func
(
typedData
*
TypedData
)
Map
()
map
[
string
]
interface
{}
{
dataMap
:=
map
[
string
]
interface
{}{
"types"
:
typedData
.
Types
,
"domain"
:
typedData
.
Domain
.
Map
(),
"primaryType"
:
typedData
.
PrimaryType
,
"message"
:
typedData
.
Message
,
}
return
dataMap
}
// Format returns a representation of typedData, which can be easily displayed by a user-interface
// without in-depth knowledge about 712 rules
func
(
typedData
*
TypedData
)
Format
()
([]
*
NameValueType
,
error
)
{
domain
,
err
:=
typedData
.
formatData
(
"EIP712Domain"
,
typedData
.
Domain
.
Map
())
if
err
!=
nil
{
return
nil
,
err
}
ptype
,
err
:=
typedData
.
formatData
(
typedData
.
PrimaryType
,
typedData
.
Message
)
if
err
!=
nil
{
return
nil
,
err
}
var
nvts
[]
*
NameValueType
nvts
=
append
(
nvts
,
&
NameValueType
{
Name
:
"EIP712Domain"
,
Value
:
domain
,
Typ
:
"domain"
,
})
nvts
=
append
(
nvts
,
&
NameValueType
{
Name
:
typedData
.
PrimaryType
,
Value
:
ptype
,
Typ
:
"primary type"
,
})
return
nvts
,
nil
}
func
(
typedData
*
TypedData
)
formatData
(
primaryType
string
,
data
map
[
string
]
interface
{})
([]
*
NameValueType
,
error
)
{
var
output
[]
*
NameValueType
// Add field contents. Structs and arrays have special handlers.
for
_
,
field
:=
range
typedData
.
Types
[
primaryType
]
{
encName
:=
field
.
Name
encValue
:=
data
[
encName
]
item
:=
&
NameValueType
{
Name
:
encName
,
Typ
:
field
.
Type
,
}
if
field
.
isArray
()
{
arrayValue
,
_
:=
encValue
.
([]
interface
{})
parsedType
:=
field
.
typeName
()
for
_
,
v
:=
range
arrayValue
{
if
typedData
.
Types
[
parsedType
]
!=
nil
{
mapValue
,
_
:=
v
.
(
map
[
string
]
interface
{})
mapOutput
,
err
:=
typedData
.
formatData
(
parsedType
,
mapValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
mapOutput
}
else
{
primitiveOutput
,
err
:=
formatPrimitiveValue
(
field
.
Type
,
encValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
primitiveOutput
}
}
}
else
if
typedData
.
Types
[
field
.
Type
]
!=
nil
{
if
mapValue
,
ok
:=
encValue
.
(
map
[
string
]
interface
{});
ok
{
mapOutput
,
err
:=
typedData
.
formatData
(
field
.
Type
,
mapValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
mapOutput
}
else
{
item
.
Value
=
"<nil>"
}
}
else
{
primitiveOutput
,
err
:=
formatPrimitiveValue
(
field
.
Type
,
encValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
primitiveOutput
}
output
=
append
(
output
,
item
)
}
return
output
,
nil
}
func
formatPrimitiveValue
(
encType
string
,
encValue
interface
{})
(
string
,
error
)
{
switch
encType
{
case
"address"
:
if
stringValue
,
ok
:=
encValue
.
(
string
);
!
ok
{
return
""
,
fmt
.
Errorf
(
"could not format value %v as address"
,
encValue
)
}
else
{
return
common
.
HexToAddress
(
stringValue
)
.
String
(),
nil
}
case
"bool"
:
if
boolValue
,
ok
:=
encValue
.
(
bool
);
!
ok
{
return
""
,
fmt
.
Errorf
(
"could not format value %v as bool"
,
encValue
)
}
else
{
return
fmt
.
Sprintf
(
"%t"
,
boolValue
),
nil
}
case
"bytes"
,
"string"
:
return
fmt
.
Sprintf
(
"%s"
,
encValue
),
nil
}
if
strings
.
HasPrefix
(
encType
,
"bytes"
)
{
return
fmt
.
Sprintf
(
"%s"
,
encValue
),
nil
}
if
strings
.
HasPrefix
(
encType
,
"uint"
)
||
strings
.
HasPrefix
(
encType
,
"int"
)
{
if
b
,
err
:=
parseInteger
(
encType
,
encValue
);
err
!=
nil
{
return
""
,
err
}
else
{
return
fmt
.
Sprintf
(
"%d (0x%x)"
,
b
,
b
),
nil
}
}
return
""
,
fmt
.
Errorf
(
"unhandled type %v"
,
encType
)
}
// Validate checks if the types object is conformant to the specs
func
(
t
Types
)
validate
()
error
{
for
typeKey
,
typeArr
:=
range
t
{
if
len
(
typeKey
)
==
0
{
return
fmt
.
Errorf
(
"empty type key"
)
}
for
i
,
typeObj
:=
range
typeArr
{
if
len
(
typeObj
.
Type
)
==
0
{
return
fmt
.
Errorf
(
"type %q:%d: empty Type"
,
typeKey
,
i
)
}
if
len
(
typeObj
.
Name
)
==
0
{
return
fmt
.
Errorf
(
"type %q:%d: empty Name"
,
typeKey
,
i
)
}
if
typeKey
==
typeObj
.
Type
{
return
fmt
.
Errorf
(
"type %q cannot reference itself"
,
typeObj
.
Type
)
}
if
typeObj
.
isReferenceType
()
{
if
_
,
exist
:=
t
[
typeObj
.
typeName
()];
!
exist
{
return
fmt
.
Errorf
(
"reference type %q is undefined"
,
typeObj
.
Type
)
}
if
!
typedDataReferenceTypeRegexp
.
MatchString
(
typeObj
.
Type
)
{
return
fmt
.
Errorf
(
"unknown reference type %q"
,
typeObj
.
Type
)
}
}
else
if
!
isPrimitiveTypeValid
(
typeObj
.
Type
)
{
return
fmt
.
Errorf
(
"unknown type %q"
,
typeObj
.
Type
)
}
}
}
return
nil
}
// Checks if the primitive value is valid
func
isPrimitiveTypeValid
(
primitiveType
string
)
bool
{
if
primitiveType
==
"address"
||
primitiveType
==
"address[]"
||
primitiveType
==
"bool"
||
primitiveType
==
"bool[]"
||
primitiveType
==
"string"
||
primitiveType
==
"string[]"
{
return
true
}
if
primitiveType
==
"bytes"
||
primitiveType
==
"bytes[]"
||
primitiveType
==
"bytes1"
||
primitiveType
==
"bytes1[]"
||
primitiveType
==
"bytes2"
||
primitiveType
==
"bytes2[]"
||
primitiveType
==
"bytes3"
||
primitiveType
==
"bytes3[]"
||
primitiveType
==
"bytes4"
||
primitiveType
==
"bytes4[]"
||
primitiveType
==
"bytes5"
||
primitiveType
==
"bytes5[]"
||
primitiveType
==
"bytes6"
||
primitiveType
==
"bytes6[]"
||
primitiveType
==
"bytes7"
||
primitiveType
==
"bytes7[]"
||
primitiveType
==
"bytes8"
||
primitiveType
==
"bytes8[]"
||
primitiveType
==
"bytes9"
||
primitiveType
==
"bytes9[]"
||
primitiveType
==
"bytes10"
||
primitiveType
==
"bytes10[]"
||
primitiveType
==
"bytes11"
||
primitiveType
==
"bytes11[]"
||
primitiveType
==
"bytes12"
||
primitiveType
==
"bytes12[]"
||
primitiveType
==
"bytes13"
||
primitiveType
==
"bytes13[]"
||
primitiveType
==
"bytes14"
||
primitiveType
==
"bytes14[]"
||
primitiveType
==
"bytes15"
||
primitiveType
==
"bytes15[]"
||
primitiveType
==
"bytes16"
||
primitiveType
==
"bytes16[]"
||
primitiveType
==
"bytes17"
||
primitiveType
==
"bytes17[]"
||
primitiveType
==
"bytes18"
||
primitiveType
==
"bytes18[]"
||
primitiveType
==
"bytes19"
||
primitiveType
==
"bytes19[]"
||
primitiveType
==
"bytes20"
||
primitiveType
==
"bytes20[]"
||
primitiveType
==
"bytes21"
||
primitiveType
==
"bytes21[]"
||
primitiveType
==
"bytes22"
||
primitiveType
==
"bytes22[]"
||
primitiveType
==
"bytes23"
||
primitiveType
==
"bytes23[]"
||
primitiveType
==
"bytes24"
||
primitiveType
==
"bytes24[]"
||
primitiveType
==
"bytes25"
||
primitiveType
==
"bytes25[]"
||
primitiveType
==
"bytes26"
||
primitiveType
==
"bytes26[]"
||
primitiveType
==
"bytes27"
||
primitiveType
==
"bytes27[]"
||
primitiveType
==
"bytes28"
||
primitiveType
==
"bytes28[]"
||
primitiveType
==
"bytes29"
||
primitiveType
==
"bytes29[]"
||
primitiveType
==
"bytes30"
||
primitiveType
==
"bytes30[]"
||
primitiveType
==
"bytes31"
||
primitiveType
==
"bytes31[]"
||
primitiveType
==
"bytes32"
||
primitiveType
==
"bytes32[]"
{
return
true
}
if
primitiveType
==
"int"
||
primitiveType
==
"int[]"
||
primitiveType
==
"int8"
||
primitiveType
==
"int8[]"
||
primitiveType
==
"int16"
||
primitiveType
==
"int16[]"
||
primitiveType
==
"int32"
||
primitiveType
==
"int32[]"
||
primitiveType
==
"int64"
||
primitiveType
==
"int64[]"
||
primitiveType
==
"int128"
||
primitiveType
==
"int128[]"
||
primitiveType
==
"int256"
||
primitiveType
==
"int256[]"
{
return
true
}
if
primitiveType
==
"uint"
||
primitiveType
==
"uint[]"
||
primitiveType
==
"uint8"
||
primitiveType
==
"uint8[]"
||
primitiveType
==
"uint16"
||
primitiveType
==
"uint16[]"
||
primitiveType
==
"uint32"
||
primitiveType
==
"uint32[]"
||
primitiveType
==
"uint64"
||
primitiveType
==
"uint64[]"
||
primitiveType
==
"uint128"
||
primitiveType
==
"uint128[]"
||
primitiveType
==
"uint256"
||
primitiveType
==
"uint256[]"
{
return
true
}
return
false
}
// validate checks if the given domain is valid, i.e. contains at least
// the minimum viable keys and values
func
(
domain
*
TypedDataDomain
)
validate
()
error
{
if
domain
.
ChainId
==
nil
&&
len
(
domain
.
Name
)
==
0
&&
len
(
domain
.
Version
)
==
0
&&
len
(
domain
.
VerifyingContract
)
==
0
&&
len
(
domain
.
Salt
)
==
0
{
return
errors
.
New
(
"domain is undefined"
)
}
return
nil
}
// Map is a helper function to generate a map version of the domain
func
(
domain
*
TypedDataDomain
)
Map
()
map
[
string
]
interface
{}
{
dataMap
:=
map
[
string
]
interface
{}{}
if
domain
.
ChainId
!=
nil
{
dataMap
[
"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
}
// NameValueType is a very simple struct with Name, Value and Type. It's meant for simple
// json structures used to communicate signing-info about typed data with the UI
type
NameValueType
struct
{
Name
string
`json:"name"`
Value
interface
{}
`json:"value"`
Typ
string
`json:"type"`
}
// Pprint returns a pretty-printed version of nvt
func
(
nvt
*
NameValueType
)
Pprint
(
depth
int
)
string
{
output
:=
bytes
.
Buffer
{}
output
.
WriteString
(
strings
.
Repeat
(
"
\u00a0
"
,
depth
*
2
))
output
.
WriteString
(
fmt
.
Sprintf
(
"%s [%s]: "
,
nvt
.
Name
,
nvt
.
Typ
))
if
nvts
,
ok
:=
nvt
.
Value
.
([]
*
NameValueType
);
ok
{
output
.
WriteString
(
"
\n
"
)
for
_
,
next
:=
range
nvts
{
sublevel
:=
next
.
Pprint
(
depth
+
1
)
output
.
WriteString
(
sublevel
)
}
}
else
{
if
nvt
.
Value
!=
nil
{
output
.
WriteString
(
fmt
.
Sprintf
(
"%q
\n
"
,
nvt
.
Value
))
}
else
{
output
.
WriteString
(
"
\n
"
)
}
}
return
output
.
String
()
}
signer/core/auditlog.go
View file @
619a3e70
...
@@ -89,7 +89,7 @@ func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.Mixedcas
...
@@ -89,7 +89,7 @@ func (l *AuditLogger) SignGnosisSafeTx(ctx context.Context, addr common.Mixedcas
return
res
,
e
return
res
,
e
}
}
func
(
l
*
AuditLogger
)
SignTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
data
TypedData
)
(
hexutil
.
Bytes
,
error
)
{
func
(
l
*
AuditLogger
)
SignTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
data
apitypes
.
TypedData
)
(
hexutil
.
Bytes
,
error
)
{
l
.
log
.
Info
(
"SignTypedData"
,
"type"
,
"request"
,
"metadata"
,
MetadataFromContext
(
ctx
)
.
String
(),
l
.
log
.
Info
(
"SignTypedData"
,
"type"
,
"request"
,
"metadata"
,
MetadataFromContext
(
ctx
)
.
String
(),
"addr"
,
addr
.
String
(),
"data"
,
data
)
"addr"
,
addr
.
String
(),
"data"
,
data
)
b
,
e
:=
l
.
api
.
SignTypedData
(
ctx
,
addr
,
data
)
b
,
e
:=
l
.
api
.
SignTypedData
(
ctx
,
addr
,
data
)
...
...
signer/core/gnosis_safe.go
View file @
619a3e70
...
@@ -34,15 +34,15 @@ type GnosisSafeTx struct {
...
@@ -34,15 +34,15 @@ type GnosisSafeTx struct {
}
}
// ToTypedData converts the tx to a EIP-712 Typed Data structure for signing
// ToTypedData converts the tx to a EIP-712 Typed Data structure for signing
func
(
tx
*
GnosisSafeTx
)
ToTypedData
()
TypedData
{
func
(
tx
*
GnosisSafeTx
)
ToTypedData
()
apitypes
.
TypedData
{
var
data
hexutil
.
Bytes
var
data
hexutil
.
Bytes
if
tx
.
Data
!=
nil
{
if
tx
.
Data
!=
nil
{
data
=
*
tx
.
Data
data
=
*
tx
.
Data
}
}
gnosisTypedData
:=
TypedData
{
gnosisTypedData
:=
apitypes
.
TypedData
{
Types
:
Types
{
Types
:
apitypes
.
Types
{
"EIP712Domain"
:
[]
Type
{{
Name
:
"verifyingContract"
,
Type
:
"address"
}},
"EIP712Domain"
:
[]
apitypes
.
Type
{{
Name
:
"verifyingContract"
,
Type
:
"address"
}},
"SafeTx"
:
[]
Type
{
"SafeTx"
:
[]
apitypes
.
Type
{
{
Name
:
"to"
,
Type
:
"address"
},
{
Name
:
"to"
,
Type
:
"address"
},
{
Name
:
"value"
,
Type
:
"uint256"
},
{
Name
:
"value"
,
Type
:
"uint256"
},
{
Name
:
"data"
,
Type
:
"bytes"
},
{
Name
:
"data"
,
Type
:
"bytes"
},
...
@@ -55,11 +55,11 @@ func (tx *GnosisSafeTx) ToTypedData() TypedData {
...
@@ -55,11 +55,11 @@ func (tx *GnosisSafeTx) ToTypedData() TypedData {
{
Name
:
"nonce"
,
Type
:
"uint256"
},
{
Name
:
"nonce"
,
Type
:
"uint256"
},
},
},
},
},
Domain
:
TypedDataDomain
{
Domain
:
apitypes
.
TypedDataDomain
{
VerifyingContract
:
tx
.
Safe
.
Address
()
.
Hex
(),
VerifyingContract
:
tx
.
Safe
.
Address
()
.
Hex
(),
},
},
PrimaryType
:
"SafeTx"
,
PrimaryType
:
"SafeTx"
,
Message
:
TypedDataMessage
{
Message
:
apitypes
.
TypedDataMessage
{
"to"
:
tx
.
To
.
Address
()
.
Hex
(),
"to"
:
tx
.
To
.
Address
()
.
Hex
(),
"value"
:
tx
.
Value
.
String
(),
"value"
:
tx
.
Value
.
String
(),
"data"
:
data
,
"data"
:
data
,
...
...
signer/core/signed_data.go
View file @
619a3e70
...
@@ -17,24 +17,14 @@
...
@@ -17,24 +17,14 @@
package
core
package
core
import
(
import
(
"bytes"
"context"
"context"
"errors"
"errors"
"fmt"
"fmt"
"math/big"
"mime"
"mime"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto"
...
@@ -42,88 +32,6 @@ import (
...
@@ -42,88 +32,6 @@ import (
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
)
)
type
SigFormat
struct
{
Mime
string
ByteVersion
byte
}
var
(
IntendedValidator
=
SigFormat
{
accounts
.
MimetypeDataWithValidator
,
0x00
,
}
DataTyped
=
SigFormat
{
accounts
.
MimetypeTypedData
,
0x01
,
}
ApplicationClique
=
SigFormat
{
accounts
.
MimetypeClique
,
0x02
,
}
TextPlain
=
SigFormat
{
accounts
.
MimetypeTextPlain
,
0x45
,
}
)
type
ValidatorData
struct
{
Address
common
.
Address
Message
hexutil
.
Bytes
}
type
TypedData
struct
{
Types
Types
`json:"types"`
PrimaryType
string
`json:"primaryType"`
Domain
TypedDataDomain
`json:"domain"`
Message
TypedDataMessage
`json:"message"`
}
type
Type
struct
{
Name
string
`json:"name"`
Type
string
`json:"type"`
}
func
(
t
*
Type
)
isArray
()
bool
{
return
strings
.
HasSuffix
(
t
.
Type
,
"[]"
)
}
// typeName returns the canonical name of the type. If the type is 'Person[]', then
// this method returns 'Person'
func
(
t
*
Type
)
typeName
()
string
{
if
strings
.
HasSuffix
(
t
.
Type
,
"[]"
)
{
return
strings
.
TrimSuffix
(
t
.
Type
,
"[]"
)
}
return
t
.
Type
}
func
(
t
*
Type
)
isReferenceType
()
bool
{
if
len
(
t
.
Type
)
==
0
{
return
false
}
// Reference types must have a leading uppercase character
r
,
_
:=
utf8
.
DecodeRuneInString
(
t
.
Type
)
return
unicode
.
IsUpper
(
r
)
}
type
Types
map
[
string
][]
Type
type
TypePriority
struct
{
Type
string
Value
uint
}
type
TypedDataMessage
=
map
[
string
]
interface
{}
type
TypedDataDomain
struct
{
Name
string
`json:"name"`
Version
string
`json:"version"`
ChainId
*
math
.
HexOrDecimal256
`json:"chainId"`
VerifyingContract
string
`json:"verifyingContract"`
Salt
string
`json:"salt"`
}
var
typedDataReferenceTypeRegexp
=
regexp
.
MustCompile
(
`^[A-Z](\w*)(\[\])?$`
)
// sign receives a request and produces a signature
// sign receives a request and produces a signature
//
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
...
@@ -195,14 +103,14 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
...
@@ -195,14 +103,14 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
}
}
switch
mediaType
{
switch
mediaType
{
case
IntendedValidator
.
Mime
:
case
apitypes
.
IntendedValidator
.
Mime
:
// Data with an intended validator
// Data with an intended validator
validatorData
,
err
:=
UnmarshalValidatorData
(
data
)
validatorData
,
err
:=
UnmarshalValidatorData
(
data
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
useEthereumV
,
err
return
nil
,
useEthereumV
,
err
}
}
sighash
,
msg
:=
SignTextValidator
(
validatorData
)
sighash
,
msg
:=
SignTextValidator
(
validatorData
)
messages
:=
[]
*
NameValueType
{
messages
:=
[]
*
apitypes
.
NameValueType
{
{
{
Name
:
"This is a request to sign data intended for a particular validator (see EIP 191 version 0)"
,
Name
:
"This is a request to sign data intended for a particular validator (see EIP 191 version 0)"
,
Typ
:
"description"
,
Typ
:
"description"
,
...
@@ -225,11 +133,11 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
...
@@ -225,11 +133,11 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
},
},
}
}
req
=
&
SignDataRequest
{
ContentType
:
mediaType
,
Rawdata
:
[]
byte
(
msg
),
Messages
:
messages
,
Hash
:
sighash
}
req
=
&
SignDataRequest
{
ContentType
:
mediaType
,
Rawdata
:
[]
byte
(
msg
),
Messages
:
messages
,
Hash
:
sighash
}
case
ApplicationClique
.
Mime
:
case
apitypes
.
ApplicationClique
.
Mime
:
// Clique is the Ethereum PoA standard
// Clique is the Ethereum PoA standard
stringData
,
ok
:=
data
.
(
string
)
stringData
,
ok
:=
data
.
(
string
)
if
!
ok
{
if
!
ok
{
return
nil
,
useEthereumV
,
fmt
.
Errorf
(
"input for %v must be an hex-encoded string"
,
ApplicationClique
.
Mime
)
return
nil
,
useEthereumV
,
fmt
.
Errorf
(
"input for %v must be an hex-encoded string"
,
apitypes
.
ApplicationClique
.
Mime
)
}
}
cliqueData
,
err
:=
hexutil
.
Decode
(
stringData
)
cliqueData
,
err
:=
hexutil
.
Decode
(
stringData
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -251,7 +159,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
...
@@ -251,7 +159,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
useEthereumV
,
err
return
nil
,
useEthereumV
,
err
}
}
messages
:=
[]
*
NameValueType
{
messages
:=
[]
*
apitypes
.
NameValueType
{
{
{
Name
:
"Clique header"
,
Name
:
"Clique header"
,
Typ
:
"clique"
,
Typ
:
"clique"
,
...
@@ -272,7 +180,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
...
@@ -272,7 +180,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
return
nil
,
useEthereumV
,
err
return
nil
,
useEthereumV
,
err
}
else
{
}
else
{
sighash
,
msg
:=
accounts
.
TextAndHash
(
textData
)
sighash
,
msg
:=
accounts
.
TextAndHash
(
textData
)
messages
:=
[]
*
NameValueType
{
messages
:=
[]
*
apitypes
.
NameValueType
{
{
{
Name
:
"message"
,
Name
:
"message"
,
Typ
:
accounts
.
MimetypeTextPlain
,
Typ
:
accounts
.
MimetypeTextPlain
,
...
@@ -291,7 +199,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
...
@@ -291,7 +199,7 @@ func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType
// SignTextWithValidator signs the given message which can be further recovered
// SignTextWithValidator signs the given message which can be further recovered
// with the given validator.
// with the given validator.
// hash = keccak256("\x19\x00"${address}${data}).
// hash = keccak256("\x19\x00"${address}${data}).
func
SignTextValidator
(
validatorData
ValidatorData
)
(
hexutil
.
Bytes
,
string
)
{
func
SignTextValidator
(
validatorData
apitypes
.
ValidatorData
)
(
hexutil
.
Bytes
,
string
)
{
msg
:=
fmt
.
Sprintf
(
"
\x19\x00
%s%s"
,
string
(
validatorData
.
Address
.
Bytes
()),
string
(
validatorData
.
Message
))
msg
:=
fmt
.
Sprintf
(
"
\x19\x00
%s%s"
,
string
(
validatorData
.
Address
.
Bytes
()),
string
(
validatorData
.
Message
))
return
crypto
.
Keccak256
([]
byte
(
msg
)),
msg
return
crypto
.
Keccak256
([]
byte
(
msg
)),
msg
}
}
...
@@ -318,7 +226,7 @@ func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error)
...
@@ -318,7 +226,7 @@ func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error)
// It returns
// It returns
// - the signature,
// - the signature,
// - and/or any error
// - and/or any error
func
(
api
*
SignerAPI
)
SignTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
typedData
TypedData
)
(
hexutil
.
Bytes
,
error
)
{
func
(
api
*
SignerAPI
)
SignTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
typedData
apitypes
.
TypedData
)
(
hexutil
.
Bytes
,
error
)
{
signature
,
_
,
err
:=
api
.
signTypedData
(
ctx
,
addr
,
typedData
,
nil
)
signature
,
_
,
err
:=
api
.
signTypedData
(
ctx
,
addr
,
typedData
,
nil
)
return
signature
,
err
return
signature
,
err
}
}
...
@@ -326,7 +234,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd
...
@@ -326,7 +234,7 @@ func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAd
// signTypedData is identical to the capitalized version, except that it also returns the hash (preimage)
// signTypedData is identical to the capitalized version, except that it also returns the hash (preimage)
// - the signature preimage (hash)
// - the signature preimage (hash)
func
(
api
*
SignerAPI
)
signTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
func
(
api
*
SignerAPI
)
signTypedData
(
ctx
context
.
Context
,
addr
common
.
MixedcaseAddress
,
typedData
TypedData
,
validationMessages
*
apitypes
.
ValidationMessages
)
(
hexutil
.
Bytes
,
hexutil
.
Bytes
,
error
)
{
typedData
apitypes
.
TypedData
,
validationMessages
*
apitypes
.
ValidationMessages
)
(
hexutil
.
Bytes
,
hexutil
.
Bytes
,
error
)
{
domainSeparator
,
err
:=
typedData
.
HashStruct
(
"EIP712Domain"
,
typedData
.
Domain
.
Map
())
domainSeparator
,
err
:=
typedData
.
HashStruct
(
"EIP712Domain"
,
typedData
.
Domain
.
Map
())
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
nil
,
err
return
nil
,
nil
,
err
...
@@ -342,7 +250,7 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd
...
@@ -342,7 +250,7 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd
return
nil
,
nil
,
err
return
nil
,
nil
,
err
}
}
req
:=
&
SignDataRequest
{
req
:=
&
SignDataRequest
{
ContentType
:
DataTyped
.
Mime
,
ContentType
:
apitypes
.
DataTyped
.
Mime
,
Rawdata
:
rawData
,
Rawdata
:
rawData
,
Messages
:
messages
,
Messages
:
messages
,
Hash
:
sighash
,
Hash
:
sighash
,
...
@@ -358,289 +266,6 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd
...
@@ -358,289 +266,6 @@ func (api *SignerAPI) signTypedData(ctx context.Context, addr common.MixedcaseAd
return
signature
,
sighash
,
nil
return
signature
,
sighash
,
nil
}
}
// HashStruct generates a keccak256 hash of the encoding of the provided data
func
(
typedData
*
TypedData
)
HashStruct
(
primaryType
string
,
data
TypedDataMessage
)
(
hexutil
.
Bytes
,
error
)
{
encodedData
,
err
:=
typedData
.
EncodeData
(
primaryType
,
data
,
1
)
if
err
!=
nil
{
return
nil
,
err
}
return
crypto
.
Keccak256
(
encodedData
),
nil
}
// Dependencies returns an array of custom types ordered by their hierarchical reference tree
func
(
typedData
*
TypedData
)
Dependencies
(
primaryType
string
,
found
[]
string
)
[]
string
{
includes
:=
func
(
arr
[]
string
,
str
string
)
bool
{
for
_
,
obj
:=
range
arr
{
if
obj
==
str
{
return
true
}
}
return
false
}
if
includes
(
found
,
primaryType
)
{
return
found
}
if
typedData
.
Types
[
primaryType
]
==
nil
{
return
found
}
found
=
append
(
found
,
primaryType
)
for
_
,
field
:=
range
typedData
.
Types
[
primaryType
]
{
for
_
,
dep
:=
range
typedData
.
Dependencies
(
field
.
Type
,
found
)
{
if
!
includes
(
found
,
dep
)
{
found
=
append
(
found
,
dep
)
}
}
}
return
found
}
// EncodeType generates the following encoding:
// `name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")"`
//
// each member is written as `type ‖ " " ‖ name` encodings cascade down and are sorted by name
func
(
typedData
*
TypedData
)
EncodeType
(
primaryType
string
)
hexutil
.
Bytes
{
// Get dependencies primary first, then alphabetical
deps
:=
typedData
.
Dependencies
(
primaryType
,
[]
string
{})
if
len
(
deps
)
>
0
{
slicedDeps
:=
deps
[
1
:
]
sort
.
Strings
(
slicedDeps
)
deps
=
append
([]
string
{
primaryType
},
slicedDeps
...
)
}
// Format as a string with fields
var
buffer
bytes
.
Buffer
for
_
,
dep
:=
range
deps
{
buffer
.
WriteString
(
dep
)
buffer
.
WriteString
(
"("
)
for
_
,
obj
:=
range
typedData
.
Types
[
dep
]
{
buffer
.
WriteString
(
obj
.
Type
)
buffer
.
WriteString
(
" "
)
buffer
.
WriteString
(
obj
.
Name
)
buffer
.
WriteString
(
","
)
}
buffer
.
Truncate
(
buffer
.
Len
()
-
1
)
buffer
.
WriteString
(
")"
)
}
return
buffer
.
Bytes
()
}
// TypeHash creates the keccak256 hash of the data
func
(
typedData
*
TypedData
)
TypeHash
(
primaryType
string
)
hexutil
.
Bytes
{
return
crypto
.
Keccak256
(
typedData
.
EncodeType
(
primaryType
))
}
// EncodeData generates the following encoding:
// `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)`
//
// each encoded member is 32-byte long
func
(
typedData
*
TypedData
)
EncodeData
(
primaryType
string
,
data
map
[
string
]
interface
{},
depth
int
)
(
hexutil
.
Bytes
,
error
)
{
if
err
:=
typedData
.
validate
();
err
!=
nil
{
return
nil
,
err
}
buffer
:=
bytes
.
Buffer
{}
// Verify extra data
if
exp
,
got
:=
len
(
typedData
.
Types
[
primaryType
]),
len
(
data
);
exp
<
got
{
return
nil
,
fmt
.
Errorf
(
"there is extra data provided in the message (%d < %d)"
,
exp
,
got
)
}
// Add typehash
buffer
.
Write
(
typedData
.
TypeHash
(
primaryType
))
// Add field contents. Structs and arrays have special handlers.
for
_
,
field
:=
range
typedData
.
Types
[
primaryType
]
{
encType
:=
field
.
Type
encValue
:=
data
[
field
.
Name
]
if
encType
[
len
(
encType
)
-
1
:
]
==
"]"
{
arrayValue
,
ok
:=
encValue
.
([]
interface
{})
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
arrayBuffer
:=
bytes
.
Buffer
{}
parsedType
:=
strings
.
Split
(
encType
,
"["
)[
0
]
for
_
,
item
:=
range
arrayValue
{
if
typedData
.
Types
[
parsedType
]
!=
nil
{
mapValue
,
ok
:=
item
.
(
map
[
string
]
interface
{})
if
!
ok
{
return
nil
,
dataMismatchError
(
parsedType
,
item
)
}
encodedData
,
err
:=
typedData
.
EncodeData
(
parsedType
,
mapValue
,
depth
+
1
)
if
err
!=
nil
{
return
nil
,
err
}
arrayBuffer
.
Write
(
encodedData
)
}
else
{
bytesValue
,
err
:=
typedData
.
EncodePrimitiveValue
(
parsedType
,
item
,
depth
)
if
err
!=
nil
{
return
nil
,
err
}
arrayBuffer
.
Write
(
bytesValue
)
}
}
buffer
.
Write
(
crypto
.
Keccak256
(
arrayBuffer
.
Bytes
()))
}
else
if
typedData
.
Types
[
field
.
Type
]
!=
nil
{
mapValue
,
ok
:=
encValue
.
(
map
[
string
]
interface
{})
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
encodedData
,
err
:=
typedData
.
EncodeData
(
field
.
Type
,
mapValue
,
depth
+
1
)
if
err
!=
nil
{
return
nil
,
err
}
buffer
.
Write
(
crypto
.
Keccak256
(
encodedData
))
}
else
{
byteValue
,
err
:=
typedData
.
EncodePrimitiveValue
(
encType
,
encValue
,
depth
)
if
err
!=
nil
{
return
nil
,
err
}
buffer
.
Write
(
byteValue
)
}
}
return
buffer
.
Bytes
(),
nil
}
// Attempt to parse bytes in different formats: byte array, hex string, hexutil.Bytes.
func
parseBytes
(
encType
interface
{})
([]
byte
,
bool
)
{
switch
v
:=
encType
.
(
type
)
{
case
[]
byte
:
return
v
,
true
case
hexutil
.
Bytes
:
return
v
,
true
case
string
:
bytes
,
err
:=
hexutil
.
Decode
(
v
)
if
err
!=
nil
{
return
nil
,
false
}
return
bytes
,
true
default
:
return
nil
,
false
}
}
func
parseInteger
(
encType
string
,
encValue
interface
{})
(
*
big
.
Int
,
error
)
{
var
(
length
int
signed
=
strings
.
HasPrefix
(
encType
,
"int"
)
b
*
big
.
Int
)
if
encType
==
"int"
||
encType
==
"uint"
{
length
=
256
}
else
{
lengthStr
:=
""
if
strings
.
HasPrefix
(
encType
,
"uint"
)
{
lengthStr
=
strings
.
TrimPrefix
(
encType
,
"uint"
)
}
else
{
lengthStr
=
strings
.
TrimPrefix
(
encType
,
"int"
)
}
atoiSize
,
err
:=
strconv
.
Atoi
(
lengthStr
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid size on integer: %v"
,
lengthStr
)
}
length
=
atoiSize
}
switch
v
:=
encValue
.
(
type
)
{
case
*
math
.
HexOrDecimal256
:
b
=
(
*
big
.
Int
)(
v
)
case
string
:
var
hexIntValue
math
.
HexOrDecimal256
if
err
:=
hexIntValue
.
UnmarshalText
([]
byte
(
v
));
err
!=
nil
{
return
nil
,
err
}
b
=
(
*
big
.
Int
)(
&
hexIntValue
)
case
float64
:
// JSON parses non-strings as float64. Fail if we cannot
// convert it losslessly
if
float64
(
int64
(
v
))
==
v
{
b
=
big
.
NewInt
(
int64
(
v
))
}
else
{
return
nil
,
fmt
.
Errorf
(
"invalid float value %v for type %v"
,
v
,
encType
)
}
}
if
b
==
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid integer value %v/%v for type %v"
,
encValue
,
reflect
.
TypeOf
(
encValue
),
encType
)
}
if
b
.
BitLen
()
>
length
{
return
nil
,
fmt
.
Errorf
(
"integer larger than '%v'"
,
encType
)
}
if
!
signed
&&
b
.
Sign
()
==
-
1
{
return
nil
,
fmt
.
Errorf
(
"invalid negative value for unsigned type %v"
,
encType
)
}
return
b
,
nil
}
// EncodePrimitiveValue deals with the primitive values found
// while searching through the typed data
func
(
typedData
*
TypedData
)
EncodePrimitiveValue
(
encType
string
,
encValue
interface
{},
depth
int
)
([]
byte
,
error
)
{
switch
encType
{
case
"address"
:
stringValue
,
ok
:=
encValue
.
(
string
)
if
!
ok
||
!
common
.
IsHexAddress
(
stringValue
)
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
retval
:=
make
([]
byte
,
32
)
copy
(
retval
[
12
:
],
common
.
HexToAddress
(
stringValue
)
.
Bytes
())
return
retval
,
nil
case
"bool"
:
boolValue
,
ok
:=
encValue
.
(
bool
)
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
if
boolValue
{
return
math
.
PaddedBigBytes
(
common
.
Big1
,
32
),
nil
}
return
math
.
PaddedBigBytes
(
common
.
Big0
,
32
),
nil
case
"string"
:
strVal
,
ok
:=
encValue
.
(
string
)
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
return
crypto
.
Keccak256
([]
byte
(
strVal
)),
nil
case
"bytes"
:
bytesValue
,
ok
:=
parseBytes
(
encValue
)
if
!
ok
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
return
crypto
.
Keccak256
(
bytesValue
),
nil
}
if
strings
.
HasPrefix
(
encType
,
"bytes"
)
{
lengthStr
:=
strings
.
TrimPrefix
(
encType
,
"bytes"
)
length
,
err
:=
strconv
.
Atoi
(
lengthStr
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"invalid size on bytes: %v"
,
lengthStr
)
}
if
length
<
0
||
length
>
32
{
return
nil
,
fmt
.
Errorf
(
"invalid size on bytes: %d"
,
length
)
}
if
byteValue
,
ok
:=
parseBytes
(
encValue
);
!
ok
||
len
(
byteValue
)
!=
length
{
return
nil
,
dataMismatchError
(
encType
,
encValue
)
}
else
{
// Right-pad the bits
dst
:=
make
([]
byte
,
32
)
copy
(
dst
,
byteValue
)
return
dst
,
nil
}
}
if
strings
.
HasPrefix
(
encType
,
"int"
)
||
strings
.
HasPrefix
(
encType
,
"uint"
)
{
b
,
err
:=
parseInteger
(
encType
,
encValue
)
if
err
!=
nil
{
return
nil
,
err
}
return
math
.
U256Bytes
(
b
),
nil
}
return
nil
,
fmt
.
Errorf
(
"unrecognized type '%s'"
,
encType
)
}
// dataMismatchError generates an error for a mismatch between
// the provided type and data
func
dataMismatchError
(
encType
string
,
encValue
interface
{})
error
{
return
fmt
.
Errorf
(
"provided data '%v' doesn't match type '%s'"
,
encValue
,
encType
)
}
// EcRecover recovers the address associated with the given sig.
// EcRecover recovers the address associated with the given sig.
// Only compatible with `text/plain`
// Only compatible with `text/plain`
func
(
api
*
SignerAPI
)
EcRecover
(
ctx
context
.
Context
,
data
hexutil
.
Bytes
,
sig
hexutil
.
Bytes
)
(
common
.
Address
,
error
)
{
func
(
api
*
SignerAPI
)
EcRecover
(
ctx
context
.
Context
,
data
hexutil
.
Bytes
,
sig
hexutil
.
Bytes
)
(
common
.
Address
,
error
)
{
...
@@ -671,376 +296,37 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex
...
@@ -671,376 +296,37 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hex
}
}
// UnmarshalValidatorData converts the bytes input to typed data
// UnmarshalValidatorData converts the bytes input to typed data
func
UnmarshalValidatorData
(
data
interface
{})
(
ValidatorData
,
error
)
{
func
UnmarshalValidatorData
(
data
interface
{})
(
apitypes
.
ValidatorData
,
error
)
{
raw
,
ok
:=
data
.
(
map
[
string
]
interface
{})
raw
,
ok
:=
data
.
(
map
[
string
]
interface
{})
if
!
ok
{
if
!
ok
{
return
ValidatorData
{},
errors
.
New
(
"validator input is not a map[string]interface{}"
)
return
apitypes
.
ValidatorData
{},
errors
.
New
(
"validator input is not a map[string]interface{}"
)
}
}
addr
,
ok
:=
raw
[
"address"
]
.
(
string
)
addr
,
ok
:=
raw
[
"address"
]
.
(
string
)
if
!
ok
{
if
!
ok
{
return
ValidatorData
{},
errors
.
New
(
"validator address is not sent as a string"
)
return
apitypes
.
ValidatorData
{},
errors
.
New
(
"validator address is not sent as a string"
)
}
}
addrBytes
,
err
:=
hexutil
.
Decode
(
addr
)
addrBytes
,
err
:=
hexutil
.
Decode
(
addr
)
if
err
!=
nil
{
if
err
!=
nil
{
return
ValidatorData
{},
err
return
apitypes
.
ValidatorData
{},
err
}
}
if
!
ok
||
len
(
addrBytes
)
==
0
{
if
!
ok
||
len
(
addrBytes
)
==
0
{
return
ValidatorData
{},
errors
.
New
(
"validator address is undefined"
)
return
apitypes
.
ValidatorData
{},
errors
.
New
(
"validator address is undefined"
)
}
}
message
,
ok
:=
raw
[
"message"
]
.
(
string
)
message
,
ok
:=
raw
[
"message"
]
.
(
string
)
if
!
ok
{
if
!
ok
{
return
ValidatorData
{},
errors
.
New
(
"message is not sent as a string"
)
return
apitypes
.
ValidatorData
{},
errors
.
New
(
"message is not sent as a string"
)
}
}
messageBytes
,
err
:=
hexutil
.
Decode
(
message
)
messageBytes
,
err
:=
hexutil
.
Decode
(
message
)
if
err
!=
nil
{
if
err
!=
nil
{
return
ValidatorData
{},
err
return
apitypes
.
ValidatorData
{},
err
}
}
if
!
ok
||
len
(
messageBytes
)
==
0
{
if
!
ok
||
len
(
messageBytes
)
==
0
{
return
ValidatorData
{},
errors
.
New
(
"message is undefined"
)
return
apitypes
.
ValidatorData
{},
errors
.
New
(
"message is undefined"
)
}
}
return
ValidatorData
{
return
apitypes
.
ValidatorData
{
Address
:
common
.
BytesToAddress
(
addrBytes
),
Address
:
common
.
BytesToAddress
(
addrBytes
),
Message
:
messageBytes
,
Message
:
messageBytes
,
},
nil
},
nil
}
}
// validate makes sure the types are sound
func
(
typedData
*
TypedData
)
validate
()
error
{
if
err
:=
typedData
.
Types
.
validate
();
err
!=
nil
{
return
err
}
if
err
:=
typedData
.
Domain
.
validate
();
err
!=
nil
{
return
err
}
return
nil
}
// Map generates a map version of the typed data
func
(
typedData
*
TypedData
)
Map
()
map
[
string
]
interface
{}
{
dataMap
:=
map
[
string
]
interface
{}{
"types"
:
typedData
.
Types
,
"domain"
:
typedData
.
Domain
.
Map
(),
"primaryType"
:
typedData
.
PrimaryType
,
"message"
:
typedData
.
Message
,
}
return
dataMap
}
// Format returns a representation of typedData, which can be easily displayed by a user-interface
// without in-depth knowledge about 712 rules
func
(
typedData
*
TypedData
)
Format
()
([]
*
NameValueType
,
error
)
{
domain
,
err
:=
typedData
.
formatData
(
"EIP712Domain"
,
typedData
.
Domain
.
Map
())
if
err
!=
nil
{
return
nil
,
err
}
ptype
,
err
:=
typedData
.
formatData
(
typedData
.
PrimaryType
,
typedData
.
Message
)
if
err
!=
nil
{
return
nil
,
err
}
var
nvts
[]
*
NameValueType
nvts
=
append
(
nvts
,
&
NameValueType
{
Name
:
"EIP712Domain"
,
Value
:
domain
,
Typ
:
"domain"
,
})
nvts
=
append
(
nvts
,
&
NameValueType
{
Name
:
typedData
.
PrimaryType
,
Value
:
ptype
,
Typ
:
"primary type"
,
})
return
nvts
,
nil
}
func
(
typedData
*
TypedData
)
formatData
(
primaryType
string
,
data
map
[
string
]
interface
{})
([]
*
NameValueType
,
error
)
{
var
output
[]
*
NameValueType
// Add field contents. Structs and arrays have special handlers.
for
_
,
field
:=
range
typedData
.
Types
[
primaryType
]
{
encName
:=
field
.
Name
encValue
:=
data
[
encName
]
item
:=
&
NameValueType
{
Name
:
encName
,
Typ
:
field
.
Type
,
}
if
field
.
isArray
()
{
arrayValue
,
_
:=
encValue
.
([]
interface
{})
parsedType
:=
field
.
typeName
()
for
_
,
v
:=
range
arrayValue
{
if
typedData
.
Types
[
parsedType
]
!=
nil
{
mapValue
,
_
:=
v
.
(
map
[
string
]
interface
{})
mapOutput
,
err
:=
typedData
.
formatData
(
parsedType
,
mapValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
mapOutput
}
else
{
primitiveOutput
,
err
:=
formatPrimitiveValue
(
field
.
Type
,
encValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
primitiveOutput
}
}
}
else
if
typedData
.
Types
[
field
.
Type
]
!=
nil
{
if
mapValue
,
ok
:=
encValue
.
(
map
[
string
]
interface
{});
ok
{
mapOutput
,
err
:=
typedData
.
formatData
(
field
.
Type
,
mapValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
mapOutput
}
else
{
item
.
Value
=
"<nil>"
}
}
else
{
primitiveOutput
,
err
:=
formatPrimitiveValue
(
field
.
Type
,
encValue
)
if
err
!=
nil
{
return
nil
,
err
}
item
.
Value
=
primitiveOutput
}
output
=
append
(
output
,
item
)
}
return
output
,
nil
}
func
formatPrimitiveValue
(
encType
string
,
encValue
interface
{})
(
string
,
error
)
{
switch
encType
{
case
"address"
:
if
stringValue
,
ok
:=
encValue
.
(
string
);
!
ok
{
return
""
,
fmt
.
Errorf
(
"could not format value %v as address"
,
encValue
)
}
else
{
return
common
.
HexToAddress
(
stringValue
)
.
String
(),
nil
}
case
"bool"
:
if
boolValue
,
ok
:=
encValue
.
(
bool
);
!
ok
{
return
""
,
fmt
.
Errorf
(
"could not format value %v as bool"
,
encValue
)
}
else
{
return
fmt
.
Sprintf
(
"%t"
,
boolValue
),
nil
}
case
"bytes"
,
"string"
:
return
fmt
.
Sprintf
(
"%s"
,
encValue
),
nil
}
if
strings
.
HasPrefix
(
encType
,
"bytes"
)
{
return
fmt
.
Sprintf
(
"%s"
,
encValue
),
nil
}
if
strings
.
HasPrefix
(
encType
,
"uint"
)
||
strings
.
HasPrefix
(
encType
,
"int"
)
{
if
b
,
err
:=
parseInteger
(
encType
,
encValue
);
err
!=
nil
{
return
""
,
err
}
else
{
return
fmt
.
Sprintf
(
"%d (0x%x)"
,
b
,
b
),
nil
}
}
return
""
,
fmt
.
Errorf
(
"unhandled type %v"
,
encType
)
}
// NameValueType is a very simple struct with Name, Value and Type. It's meant for simple
// json structures used to communicate signing-info about typed data with the UI
type
NameValueType
struct
{
Name
string
`json:"name"`
Value
interface
{}
`json:"value"`
Typ
string
`json:"type"`
}
// Pprint returns a pretty-printed version of nvt
func
(
nvt
*
NameValueType
)
Pprint
(
depth
int
)
string
{
output
:=
bytes
.
Buffer
{}
output
.
WriteString
(
strings
.
Repeat
(
"
\u00a0
"
,
depth
*
2
))
output
.
WriteString
(
fmt
.
Sprintf
(
"%s [%s]: "
,
nvt
.
Name
,
nvt
.
Typ
))
if
nvts
,
ok
:=
nvt
.
Value
.
([]
*
NameValueType
);
ok
{
output
.
WriteString
(
"
\n
"
)
for
_
,
next
:=
range
nvts
{
sublevel
:=
next
.
Pprint
(
depth
+
1
)
output
.
WriteString
(
sublevel
)
}
}
else
{
if
nvt
.
Value
!=
nil
{
output
.
WriteString
(
fmt
.
Sprintf
(
"%q
\n
"
,
nvt
.
Value
))
}
else
{
output
.
WriteString
(
"
\n
"
)
}
}
return
output
.
String
()
}
// Validate checks if the types object is conformant to the specs
func
(
t
Types
)
validate
()
error
{
for
typeKey
,
typeArr
:=
range
t
{
if
len
(
typeKey
)
==
0
{
return
fmt
.
Errorf
(
"empty type key"
)
}
for
i
,
typeObj
:=
range
typeArr
{
if
len
(
typeObj
.
Type
)
==
0
{
return
fmt
.
Errorf
(
"type %q:%d: empty Type"
,
typeKey
,
i
)
}
if
len
(
typeObj
.
Name
)
==
0
{
return
fmt
.
Errorf
(
"type %q:%d: empty Name"
,
typeKey
,
i
)
}
if
typeKey
==
typeObj
.
Type
{
return
fmt
.
Errorf
(
"type %q cannot reference itself"
,
typeObj
.
Type
)
}
if
typeObj
.
isReferenceType
()
{
if
_
,
exist
:=
t
[
typeObj
.
typeName
()];
!
exist
{
return
fmt
.
Errorf
(
"reference type %q is undefined"
,
typeObj
.
Type
)
}
if
!
typedDataReferenceTypeRegexp
.
MatchString
(
typeObj
.
Type
)
{
return
fmt
.
Errorf
(
"unknown reference type %q"
,
typeObj
.
Type
)
}
}
else
if
!
isPrimitiveTypeValid
(
typeObj
.
Type
)
{
return
fmt
.
Errorf
(
"unknown type %q"
,
typeObj
.
Type
)
}
}
}
return
nil
}
// Checks if the primitive value is valid
func
isPrimitiveTypeValid
(
primitiveType
string
)
bool
{
if
primitiveType
==
"address"
||
primitiveType
==
"address[]"
||
primitiveType
==
"bool"
||
primitiveType
==
"bool[]"
||
primitiveType
==
"string"
||
primitiveType
==
"string[]"
{
return
true
}
if
primitiveType
==
"bytes"
||
primitiveType
==
"bytes[]"
||
primitiveType
==
"bytes1"
||
primitiveType
==
"bytes1[]"
||
primitiveType
==
"bytes2"
||
primitiveType
==
"bytes2[]"
||
primitiveType
==
"bytes3"
||
primitiveType
==
"bytes3[]"
||
primitiveType
==
"bytes4"
||
primitiveType
==
"bytes4[]"
||
primitiveType
==
"bytes5"
||
primitiveType
==
"bytes5[]"
||
primitiveType
==
"bytes6"
||
primitiveType
==
"bytes6[]"
||
primitiveType
==
"bytes7"
||
primitiveType
==
"bytes7[]"
||
primitiveType
==
"bytes8"
||
primitiveType
==
"bytes8[]"
||
primitiveType
==
"bytes9"
||
primitiveType
==
"bytes9[]"
||
primitiveType
==
"bytes10"
||
primitiveType
==
"bytes10[]"
||
primitiveType
==
"bytes11"
||
primitiveType
==
"bytes11[]"
||
primitiveType
==
"bytes12"
||
primitiveType
==
"bytes12[]"
||
primitiveType
==
"bytes13"
||
primitiveType
==
"bytes13[]"
||
primitiveType
==
"bytes14"
||
primitiveType
==
"bytes14[]"
||
primitiveType
==
"bytes15"
||
primitiveType
==
"bytes15[]"
||
primitiveType
==
"bytes16"
||
primitiveType
==
"bytes16[]"
||
primitiveType
==
"bytes17"
||
primitiveType
==
"bytes17[]"
||
primitiveType
==
"bytes18"
||
primitiveType
==
"bytes18[]"
||
primitiveType
==
"bytes19"
||
primitiveType
==
"bytes19[]"
||
primitiveType
==
"bytes20"
||
primitiveType
==
"bytes20[]"
||
primitiveType
==
"bytes21"
||
primitiveType
==
"bytes21[]"
||
primitiveType
==
"bytes22"
||
primitiveType
==
"bytes22[]"
||
primitiveType
==
"bytes23"
||
primitiveType
==
"bytes23[]"
||
primitiveType
==
"bytes24"
||
primitiveType
==
"bytes24[]"
||
primitiveType
==
"bytes25"
||
primitiveType
==
"bytes25[]"
||
primitiveType
==
"bytes26"
||
primitiveType
==
"bytes26[]"
||
primitiveType
==
"bytes27"
||
primitiveType
==
"bytes27[]"
||
primitiveType
==
"bytes28"
||
primitiveType
==
"bytes28[]"
||
primitiveType
==
"bytes29"
||
primitiveType
==
"bytes29[]"
||
primitiveType
==
"bytes30"
||
primitiveType
==
"bytes30[]"
||
primitiveType
==
"bytes31"
||
primitiveType
==
"bytes31[]"
||
primitiveType
==
"bytes32"
||
primitiveType
==
"bytes32[]"
{
return
true
}
if
primitiveType
==
"int"
||
primitiveType
==
"int[]"
||
primitiveType
==
"int8"
||
primitiveType
==
"int8[]"
||
primitiveType
==
"int16"
||
primitiveType
==
"int16[]"
||
primitiveType
==
"int32"
||
primitiveType
==
"int32[]"
||
primitiveType
==
"int64"
||
primitiveType
==
"int64[]"
||
primitiveType
==
"int128"
||
primitiveType
==
"int128[]"
||
primitiveType
==
"int256"
||
primitiveType
==
"int256[]"
{
return
true
}
if
primitiveType
==
"uint"
||
primitiveType
==
"uint[]"
||
primitiveType
==
"uint8"
||
primitiveType
==
"uint8[]"
||
primitiveType
==
"uint16"
||
primitiveType
==
"uint16[]"
||
primitiveType
==
"uint32"
||
primitiveType
==
"uint32[]"
||
primitiveType
==
"uint64"
||
primitiveType
==
"uint64[]"
||
primitiveType
==
"uint128"
||
primitiveType
==
"uint128[]"
||
primitiveType
==
"uint256"
||
primitiveType
==
"uint256[]"
{
return
true
}
return
false
}
// validate checks if the given domain is valid, i.e. contains at least
// the minimum viable keys and values
func
(
domain
*
TypedDataDomain
)
validate
()
error
{
if
domain
.
ChainId
==
nil
&&
len
(
domain
.
Name
)
==
0
&&
len
(
domain
.
Version
)
==
0
&&
len
(
domain
.
VerifyingContract
)
==
0
&&
len
(
domain
.
Salt
)
==
0
{
return
errors
.
New
(
"domain is undefined"
)
}
return
nil
}
// Map is a helper function to generate a map version of the domain
func
(
domain
*
TypedDataDomain
)
Map
()
map
[
string
]
interface
{}
{
dataMap
:=
map
[
string
]
interface
{}{}
if
domain
.
ChainId
!=
nil
{
dataMap
[
"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
}
signer/core/signed_data_test.go
View file @
619a3e70
...
@@ -32,9 +32,10 @@ import (
...
@@ -32,9 +32,10 @@ import (
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
)
)
var
typesStandard
=
core
.
Types
{
var
typesStandard
=
apitypes
.
Types
{
"EIP712Domain"
:
{
"EIP712Domain"
:
{
{
{
Name
:
"name"
,
Name
:
"name"
,
...
@@ -153,12 +154,12 @@ var jsonTypedData = `
...
@@ -153,12 +154,12 @@ var jsonTypedData = `
const
primaryType
=
"Mail"
const
primaryType
=
"Mail"
var
domainStandard
=
core
.
TypedDataDomain
{
var
domainStandard
=
apitypes
.
TypedDataDomain
{
"Ether Mail"
,
Name
:
"Ether Mail"
,
"1"
,
Version
:
"1"
,
math
.
NewHexOrDecimal256
(
1
),
ChainId
:
math
.
NewHexOrDecimal256
(
1
),
"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
,
VerifyingContract
:
"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
,
""
,
Salt
:
""
,
}
}
var
messageStandard
=
map
[
string
]
interface
{}{
var
messageStandard
=
map
[
string
]
interface
{}{
...
@@ -173,7 +174,7 @@ var messageStandard = map[string]interface{}{
...
@@ -173,7 +174,7 @@ var messageStandard = map[string]interface{}{
"contents"
:
"Hello, Bob!"
,
"contents"
:
"Hello, Bob!"
,
}
}
var
typedData
=
core
.
TypedData
{
var
typedData
=
apitypes
.
TypedData
{
Types
:
typesStandard
,
Types
:
typesStandard
,
PrimaryType
:
primaryType
,
PrimaryType
:
primaryType
,
Domain
:
domainStandard
,
Domain
:
domainStandard
,
...
@@ -194,7 +195,7 @@ func TestSignData(t *testing.T) {
...
@@ -194,7 +195,7 @@ func TestSignData(t *testing.T) {
control
.
approveCh
<-
"Y"
control
.
approveCh
<-
"Y"
control
.
inputCh
<-
"wrongpassword"
control
.
inputCh
<-
"wrongpassword"
signature
,
err
:=
api
.
SignData
(
context
.
Background
(),
core
.
TextPlain
.
Mime
,
a
,
hexutil
.
Encode
([]
byte
(
"EHLO world"
)))
signature
,
err
:=
api
.
SignData
(
context
.
Background
(),
apitypes
.
TextPlain
.
Mime
,
a
,
hexutil
.
Encode
([]
byte
(
"EHLO world"
)))
if
signature
!=
nil
{
if
signature
!=
nil
{
t
.
Errorf
(
"Expected nil-data, got %x"
,
signature
)
t
.
Errorf
(
"Expected nil-data, got %x"
,
signature
)
}
}
...
@@ -202,7 +203,7 @@ func TestSignData(t *testing.T) {
...
@@ -202,7 +203,7 @@ func TestSignData(t *testing.T) {
t
.
Errorf
(
"Expected ErrLocked! '%v'"
,
err
)
t
.
Errorf
(
"Expected ErrLocked! '%v'"
,
err
)
}
}
control
.
approveCh
<-
"No way"
control
.
approveCh
<-
"No way"
signature
,
err
=
api
.
SignData
(
context
.
Background
(),
core
.
TextPlain
.
Mime
,
a
,
hexutil
.
Encode
([]
byte
(
"EHLO world"
)))
signature
,
err
=
api
.
SignData
(
context
.
Background
(),
apitypes
.
TextPlain
.
Mime
,
a
,
hexutil
.
Encode
([]
byte
(
"EHLO world"
)))
if
signature
!=
nil
{
if
signature
!=
nil
{
t
.
Errorf
(
"Expected nil-data, got %x"
,
signature
)
t
.
Errorf
(
"Expected nil-data, got %x"
,
signature
)
}
}
...
@@ -212,7 +213,7 @@ func TestSignData(t *testing.T) {
...
@@ -212,7 +213,7 @@ func TestSignData(t *testing.T) {
// text/plain
// text/plain
control
.
approveCh
<-
"Y"
control
.
approveCh
<-
"Y"
control
.
inputCh
<-
"a_long_password"
control
.
inputCh
<-
"a_long_password"
signature
,
err
=
api
.
SignData
(
context
.
Background
(),
core
.
TextPlain
.
Mime
,
a
,
hexutil
.
Encode
([]
byte
(
"EHLO world"
)))
signature
,
err
=
api
.
SignData
(
context
.
Background
(),
apitypes
.
TextPlain
.
Mime
,
a
,
hexutil
.
Encode
([]
byte
(
"EHLO world"
)))
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatal
(
err
)
t
.
Fatal
(
err
)
}
}
...
@@ -232,13 +233,13 @@ func TestSignData(t *testing.T) {
...
@@ -232,13 +233,13 @@ func TestSignData(t *testing.T) {
}
}
func
TestDomainChainId
(
t
*
testing
.
T
)
{
func
TestDomainChainId
(
t
*
testing
.
T
)
{
withoutChainID
:=
core
.
TypedData
{
withoutChainID
:=
apitypes
.
TypedData
{
Types
:
core
.
Types
{
Types
:
apitypes
.
Types
{
"EIP712Domain"
:
[]
core
.
Type
{
"EIP712Domain"
:
[]
apitypes
.
Type
{
{
Name
:
"name"
,
Type
:
"string"
},
{
Name
:
"name"
,
Type
:
"string"
},
},
},
},
},
Domain
:
core
.
TypedDataDomain
{
Domain
:
apitypes
.
TypedDataDomain
{
Name
:
"test"
,
Name
:
"test"
,
},
},
}
}
...
@@ -250,14 +251,14 @@ func TestDomainChainId(t *testing.T) {
...
@@ -250,14 +251,14 @@ func TestDomainChainId(t *testing.T) {
if
_
,
err
:=
withoutChainID
.
HashStruct
(
"EIP712Domain"
,
withoutChainID
.
Domain
.
Map
());
err
!=
nil
{
if
_
,
err
:=
withoutChainID
.
HashStruct
(
"EIP712Domain"
,
withoutChainID
.
Domain
.
Map
());
err
!=
nil
{
t
.
Errorf
(
"Expected the typedData to encode the domain successfully, got %v"
,
err
)
t
.
Errorf
(
"Expected the typedData to encode the domain successfully, got %v"
,
err
)
}
}
withChainID
:=
core
.
TypedData
{
withChainID
:=
apitypes
.
TypedData
{
Types
:
core
.
Types
{
Types
:
apitypes
.
Types
{
"EIP712Domain"
:
[]
core
.
Type
{
"EIP712Domain"
:
[]
apitypes
.
Type
{
{
Name
:
"name"
,
Type
:
"string"
},
{
Name
:
"name"
,
Type
:
"string"
},
{
Name
:
"chainId"
,
Type
:
"uint256"
},
{
Name
:
"chainId"
,
Type
:
"uint256"
},
},
},
},
},
Domain
:
core
.
TypedDataDomain
{
Domain
:
apitypes
.
TypedDataDomain
{
Name
:
"test"
,
Name
:
"test"
,
ChainId
:
math
.
NewHexOrDecimal256
(
1
),
ChainId
:
math
.
NewHexOrDecimal256
(
1
),
},
},
...
@@ -323,7 +324,7 @@ func TestEncodeData(t *testing.T) {
...
@@ -323,7 +324,7 @@ func TestEncodeData(t *testing.T) {
}
}
func
TestFormatter
(
t
*
testing
.
T
)
{
func
TestFormatter
(
t
*
testing
.
T
)
{
var
d
core
.
TypedData
var
d
apitypes
.
TypedData
err
:=
json
.
Unmarshal
([]
byte
(
jsonTypedData
),
&
d
)
err
:=
json
.
Unmarshal
([]
byte
(
jsonTypedData
),
&
d
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"unmarshalling failed '%v'"
,
err
)
t
.
Fatalf
(
"unmarshalling failed '%v'"
,
err
)
...
@@ -337,7 +338,7 @@ func TestFormatter(t *testing.T) {
...
@@ -337,7 +338,7 @@ func TestFormatter(t *testing.T) {
t
.
Logf
(
"'%v'
\n
"
,
string
(
j
))
t
.
Logf
(
"'%v'
\n
"
,
string
(
j
))
}
}
func
sign
(
typedData
core
.
TypedData
)
([]
byte
,
[]
byte
,
error
)
{
func
sign
(
typedData
apitypes
.
TypedData
)
([]
byte
,
[]
byte
,
error
)
{
domainSeparator
,
err
:=
typedData
.
HashStruct
(
"EIP712Domain"
,
typedData
.
Domain
.
Map
())
domainSeparator
,
err
:=
typedData
.
HashStruct
(
"EIP712Domain"
,
typedData
.
Domain
.
Map
())
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
nil
,
err
return
nil
,
nil
,
err
...
@@ -366,7 +367,7 @@ func TestJsonFiles(t *testing.T) {
...
@@ -366,7 +367,7 @@ func TestJsonFiles(t *testing.T) {
t
.
Errorf
(
"Failed to read file %v: %v"
,
fInfo
.
Name
(),
err
)
t
.
Errorf
(
"Failed to read file %v: %v"
,
fInfo
.
Name
(),
err
)
continue
continue
}
}
var
typedData
core
.
TypedData
var
typedData
apitypes
.
TypedData
err
=
json
.
Unmarshal
(
data
,
&
typedData
)
err
=
json
.
Unmarshal
(
data
,
&
typedData
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Errorf
(
"Test %d, file %v, json unmarshalling failed: %v"
,
i
,
fInfo
.
Name
(),
err
)
t
.
Errorf
(
"Test %d, file %v, json unmarshalling failed: %v"
,
i
,
fInfo
.
Name
(),
err
)
...
@@ -398,7 +399,7 @@ func TestFuzzerFiles(t *testing.T) {
...
@@ -398,7 +399,7 @@ func TestFuzzerFiles(t *testing.T) {
t
.
Errorf
(
"Failed to read file %v: %v"
,
fInfo
.
Name
(),
err
)
t
.
Errorf
(
"Failed to read file %v: %v"
,
fInfo
.
Name
(),
err
)
continue
continue
}
}
var
typedData
core
.
TypedData
var
typedData
apitypes
.
TypedData
err
=
json
.
Unmarshal
(
data
,
&
typedData
)
err
=
json
.
Unmarshal
(
data
,
&
typedData
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Errorf
(
"Test %d, file %v, json unmarshalling failed: %v"
,
i
,
fInfo
.
Name
(),
err
)
t
.
Errorf
(
"Test %d, file %v, json unmarshalling failed: %v"
,
i
,
fInfo
.
Name
(),
err
)
...
@@ -498,7 +499,7 @@ var gnosisTx = `
...
@@ -498,7 +499,7 @@ var gnosisTx = `
// TestGnosisTypedData tests the scenario where a user submits a full EIP-712
// TestGnosisTypedData tests the scenario where a user submits a full EIP-712
// struct without using the gnosis-specific endpoint
// struct without using the gnosis-specific endpoint
func
TestGnosisTypedData
(
t
*
testing
.
T
)
{
func
TestGnosisTypedData
(
t
*
testing
.
T
)
{
var
td
core
.
TypedData
var
td
apitypes
.
TypedData
err
:=
json
.
Unmarshal
([]
byte
(
gnosisTypedData
),
&
td
)
err
:=
json
.
Unmarshal
([]
byte
(
gnosisTypedData
),
&
td
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Fatalf
(
"unmarshalling failed '%v'"
,
err
)
t
.
Fatalf
(
"unmarshalling failed '%v'"
,
err
)
...
...
signer/rules/rules_test.go
View file @
619a3e70
...
@@ -605,7 +605,7 @@ function ApproveSignData(r){
...
@@ -605,7 +605,7 @@ function ApproveSignData(r){
t
.
Logf
(
"address %v %v
\n
"
,
addr
.
String
(),
addr
.
Original
())
t
.
Logf
(
"address %v %v
\n
"
,
addr
.
String
(),
addr
.
Original
())
nvt
:=
[]
*
core
.
NameValueType
{
nvt
:=
[]
*
apitypes
.
NameValueType
{
{
{
Name
:
"message"
,
Name
:
"message"
,
Typ
:
"text/plain"
,
Typ
:
"text/plain"
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment