Commit 572baae1 authored by Paul Berg's avatar Paul Berg Committed by Martin Holst Swende

signer, clef: implement EIP191/712 (#17789)

* Named functions and defined a basic EIP191 content type list

* Written basic content type functions

* Added ecRecover method in the clef api

* Updated the extapi changelog and addded indications in the README

* Changed the version of the external API

* Added tests for 0x45

* Implementing UnmarshalJSON() for TypedData

* Working on TypedData

* Solved the auditlog issue

* Changed method to signTypedData

* Changed mimes and implemented the 'encodeType' function for EIP-712

* Polished docstrings, ran goimports and swapped fmt.Errorf with errors.New where possible

* Drafted recursive encodeData

* Ran goimports and gofmt

* Drafted first version of EIP-712, including tests

* Temporarily switched to using common.Address in tests

* Drafted text/validator and and rewritten []byte as hexutil.Bytes

* Solved stringified address encoding issue

* Changed the property type required by signData from bytes to interface{}

* Fixed bugs in 'data/typed' signs

* Brought legal warning back after temporarily disabling it for development

* Added example RPC calls for account_signData and account_signTypedData

* Named functions and defined a basic EIP191 content type list

* Written basic content type functions

* Added ecRecover method in the clef api

* Updated the extapi changelog and addded indications in the README

* Added tests for 0x45

* Implementing UnmarshalJSON() for TypedData

* Working on TypedData

* Solved the auditlog issue

* Changed method to signTypedData

* Changed mimes and implemented the 'encodeType' function for EIP-712

* Polished docstrings, ran goimports and swapped fmt.Errorf with errors.New where possible

* Drafted recursive encodeData

* Ran goimports and gofmt

* Drafted first version of EIP-712, including tests

* Temporarily switched to using common.Address in tests

* Drafted text/validator and and rewritten []byte as hexutil.Bytes

* Solved stringified address encoding issue

* Changed the property type required by signData from bytes to interface{}

* Fixed bugs in 'data/typed' signs

* Brought legal warning back after temporarily disabling it for development

* Added example RPC calls for account_signData and account_signTypedData

* Polished and fixed PR

* Polished and fixed PR

* Solved malformed data panics and also wrote tests

* Solved malformed data panics and also wrote tests

* Added alphabetical sorting to type dependencies

* Added alphabetical sorting to type dependencies

* Added pretty print to data/typed UI

* Added pretty print to data/typed UI

* signer: more tests for typed data

* signer: more tests for typed data

* Fixed TestMalformedData4 errors and renamed IsValid to Validate

* Fixed TestMalformedData4 errors and renamed IsValid to Validate

* Fixed more new failing tests and deanonymised some functions

* Fixed more new failing tests and deanonymised some functions

* Added types to EIP712 output in cliui

* Added types to EIP712 output in cliui

* Fixed regexp issues

* Fixed regexp issues

* Added pseudo-failing test

* Added pseudo-failing test

* Fixed false positive test

* Fixed false positive test

* Added PrettyPrint method

* Added PrettyPrint method

* signer: refactor formatting and UI

* signer: make ui use new message format for signing

* Fixed breaking changes

* Fixed rules_test failing test

* Added extra regexp for reference types

* signer: more hard types

* Fixed failing test, formatted files

* signer: use golang/x keccak

* Fixed goimports error

* clef, signer: address some review concerns

* Implemented latest recommendations

* Fixed comments and uintint256 issue

* accounts, signer: fix mimetypes, add interface to sign data with passphrase

* signer, accounts: remove duplicated code, pass hash preimages to signing

* signer: prevent panic in type assertions, make cliui print rawdata as quotable-safe

* signer: linter fixes, remove deprecated crypto dependency

* accounts: fix goimport
parent 7c60d0a6
...@@ -35,6 +35,13 @@ type Account struct { ...@@ -35,6 +35,13 @@ type Account struct {
URL URL `json:"url"` // Optional resource locator within a backend URL URL `json:"url"` // Optional resource locator within a backend
} }
const (
MimetypeTextWithValidator = "text/validator"
MimetypeTypedData = "data/typed"
MimetypeClique = "application/x-clique-header"
MimetypeTextPlain = "text/plain"
)
// Wallet represents a software or hardware wallet that might contain one or more // Wallet represents a software or hardware wallet that might contain one or more
// accounts (derived from the same seed). // accounts (derived from the same seed).
type Wallet interface { type Wallet interface {
...@@ -101,6 +108,12 @@ type Wallet interface { ...@@ -101,6 +108,12 @@ type Wallet interface {
// the account in a keystore). // the account in a keystore).
SignData(account Account, mimeType string, data []byte) ([]byte, error) SignData(account Account, mimeType string, data []byte) ([]byte, error)
// SignDataWithPassphrase is identical to SignData, but also takes a password
// NOTE: there's an chance that an erroneous call might mistake the two strings, and
// supply password in the mimetype field, or vice versa. Thus, an implementation
// should never echo the mimetype or return the mimetype in the error-response
SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error)
// Signtext requests the wallet to sign the hash of a given piece of data, prefixed // Signtext requests the wallet to sign the hash of a given piece of data, prefixed
// by the Ethereum prefix scheme // by the Ethereum prefix scheme
// It looks up the account specified either solely via its address contained within, // It looks up the account specified either solely via its address contained within,
...@@ -114,6 +127,9 @@ type Wallet interface { ...@@ -114,6 +127,9 @@ type Wallet interface {
// the account in a keystore). // the account in a keystore).
SignText(account Account, text []byte) ([]byte, error) SignText(account Account, text []byte) ([]byte, error)
// SignTextWithPassphrase is identical to Signtext, but also takes a password
SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
// SignTx requests the wallet to sign the given transaction. // SignTx requests the wallet to sign the given transaction.
// //
// It looks up the account specified either solely via its address contained within, // It looks up the account specified either solely via its address contained within,
...@@ -127,18 +143,7 @@ type Wallet interface { ...@@ -127,18 +143,7 @@ type Wallet interface {
// the account in a keystore). // the account in a keystore).
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
// SignTextWithPassphrase requests the wallet to sign the given text with the // SignTxWithPassphrase is identical to SignTx, but also takes a password
// given passphrase as extra authentication information.
//
// It looks up the account specified either solely via its address contained within,
// or optionally with the aid of any location metadata from the embedded URL field.
SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
// SignTxWithPassphrase requests the wallet to sign the given transaction, with the
// given passphrase as extra authentication information.
//
// It looks up the account specified either solely via its address contained within,
// or optionally with the aid of any location metadata from the embedded URL field.
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
} }
...@@ -170,9 +175,22 @@ type Backend interface { ...@@ -170,9 +175,22 @@ type Backend interface {
// //
// This gives context to the signed message and prevents signing of transactions. // This gives context to the signed message and prevents signing of transactions.
func TextHash(data []byte) []byte { func TextHash(data []byte) []byte {
hash := sha3.NewLegacyKeccak256() hash, _ := TextAndHash(data)
fmt.Fprintf(hash, "\x19Ethereum Signed Message:\n%d%s", len(data), data) return hash
return hash.Sum(nil) }
// TextAndHash 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 calulcated as
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func TextAndHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
hasher := sha3.NewLegacyKeccak256()
hasher.Write([]byte(msg))
return hasher.Sum(nil), msg
} }
// WalletEventType represents the different event types that can be fired by // WalletEventType represents the different event types that can be fired by
......
...@@ -184,11 +184,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio ...@@ -184,11 +184,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
} }
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
return []byte{}, fmt.Errorf("operation not supported on external signers") return []byte{}, fmt.Errorf("passphrase-operations not supported on external signers")
} }
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
return nil, fmt.Errorf("operation not supported on external signers") return nil, fmt.Errorf("passphrase-operations not supported on external signers")
}
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
return nil, fmt.Errorf("passphrase-operations not supported on external signers")
} }
func (api *ExternalSigner) listAccounts() ([]common.Address, error) { func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
...@@ -201,7 +204,7 @@ func (api *ExternalSigner) listAccounts() ([]common.Address, error) { ...@@ -201,7 +204,7 @@ func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
func (api *ExternalSigner) signCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) { func (api *ExternalSigner) signCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) {
var sig hexutil.Bytes var sig hexutil.Bytes
if err := api.client.Call(&sig, "account_signData", "application/clique", a, rlpBlock); err != nil { if err := api.client.Call(&sig, "account_signData", core.ApplicationClique.Mime, a, rlpBlock); err != nil {
return nil, err return nil, err
} }
if sig[64] != 27 && sig[64] != 28 { if sig[64] != 27 && sig[64] != 28 {
......
...@@ -97,21 +97,18 @@ func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, dat ...@@ -97,21 +97,18 @@ func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, dat
return w.signHash(account, crypto.Keccak256(data)) return w.signHash(account, crypto.Keccak256(data))
} }
func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) { // SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed
return w.signHash(account, accounts.TextHash(text)) func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
}
// SignTx implements accounts.Wallet, attempting to sign the given transaction
// with the given account. If the wallet does not wrap this particular account,
// an error is returned to avoid account leakage (even though in theory we may
// be able to sign via our shared keystore backend).
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// Make sure the requested account is contained within // Make sure the requested account is contained within
if !w.Contains(account) { if !w.Contains(account) {
return nil, accounts.ErrUnknownAccount return nil, accounts.ErrUnknownAccount
} }
// Account seems valid, request the keystore to sign // Account seems valid, request the keystore to sign
return w.keystore.SignTx(account, tx, chainID) return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data))
}
func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
return w.signHash(account, accounts.TextHash(text))
} }
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the // SignHashWithPassphrase implements accounts.Wallet, attempting to sign the
...@@ -125,6 +122,19 @@ func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passph ...@@ -125,6 +122,19 @@ func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passph
return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text)) return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
} }
// SignTx implements accounts.Wallet, attempting to sign the given transaction
// with the given account. If the wallet does not wrap this particular account,
// an error is returned to avoid account leakage (even though in theory we may
// be able to sign via our shared keystore backend).
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// Make sure the requested account is contained within
if !w.Contains(account) {
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.SignTx(account, tx, chainID)
}
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
// transaction with the given account using passphrase as extra authentication. // transaction with the given account using passphrase as extra authentication.
func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
......
...@@ -507,6 +507,13 @@ func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte ...@@ -507,6 +507,13 @@ func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte
return w.signHash(account, crypto.Keccak256(data)) return w.signHash(account, crypto.Keccak256(data))
} }
// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given
// data with the given account using passphrase as extra authentication.
// Since USB wallets don't rely on passphrases, these are silently ignored.
func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
return w.SignData(account, mimeType, data)
}
func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
return w.signHash(account, accounts.TextHash(text)) return w.signHash(account, accounts.TextHash(text))
} }
......
...@@ -189,7 +189,9 @@ None ...@@ -189,7 +189,9 @@ None
"method": "account_new", "method": "account_new",
"params": [] "params": []
} }
```
Response
```
{ {
"id": 0, "id": 0,
"jsonrpc": "2.0", "jsonrpc": "2.0",
...@@ -222,7 +224,9 @@ None ...@@ -222,7 +224,9 @@ None
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "account_list" "method": "account_list"
} }
```
Response
```
{ {
"id": 1, "id": 1,
"jsonrpc": "2.0", "jsonrpc": "2.0",
...@@ -285,8 +289,8 @@ Response ...@@ -285,8 +289,8 @@ Response
```json ```json
{ {
"id": 2,
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": 67,
"error": { "error": {
"code": -32000, "code": -32000,
"message": "Request denied" "message": "Request denied"
...@@ -298,6 +302,7 @@ Response ...@@ -298,6 +302,7 @@ Response
```json ```json
{ {
"id": 67,
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "account_signTransaction", "method": "account_signTransaction",
"params": [ "params": [
...@@ -311,8 +316,7 @@ Response ...@@ -311,8 +316,7 @@ Response
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"
}, },
"safeSend(address)" "safeSend(address)"
], ]
"id": 67
} }
``` ```
Response Response
...@@ -346,15 +350,18 @@ Bash example: ...@@ -346,15 +350,18 @@ Bash example:
{"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} {"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}}
``` ```
### account_signData
### account_sign
#### Sign data #### Sign data
Signs a chunk of data and returns the calculated signature. Signs a chunk of data and returns the calculated signature.
#### Arguments #### Arguments
- content type [string]: type of signed data
- `text/validator`: hex data with custom validator defined in a contract
- `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers
- `text/plain`: simple hex data validated by `account_ecRecover`
- account [address]: account to sign with - account [address]: account to sign with
- data [data]: data to sign - data [object]: data to sign
#### Result #### Result
- calculated signature [data] - calculated signature [data]
...@@ -364,8 +371,9 @@ Bash example: ...@@ -364,8 +371,9 @@ Bash example:
{ {
"id": 3, "id": 3,
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "account_sign", "method": "account_signData",
"params": [ "params": [
"data/plain",
"0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
"0xaabbccdd" "0xaabbccdd"
] ]
...@@ -381,11 +389,109 @@ Response ...@@ -381,11 +389,109 @@ Response
} }
``` ```
### account_signTypedData
#### Sign data
Signs a chunk of structured data conformant to [EIP712]([EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md)) and returns the calculated signature.
#### Arguments
- account [address]: account to sign with
- data [object]: data to sign
#### Result
- calculated signature [data]
#### Sample call
```json
{
"id": 68,
"jsonrpc": "2.0",
"method": "account_signTypedData",
"params": [
"0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826",
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Person": [
{
"name": "name",
"type": "string"
},
{
"name": "wallet",
"type": "address"
}
],
"Mail": [
{
"name": "from",
"type": "Person"
},
{
"name": "to",
"type": "Person"
},
{
"name": "contents",
"type": "string"
}
]
},
"primaryType": "Mail",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
]
}
```
Response
```json
{
"id": 1,
"jsonrpc": "2.0",
"result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
}
```
### account_ecRecover ### account_ecRecover
#### Recover address #### Sign data
Derive the address from the account that was used to sign data from the data and signature.
Derive the address from the account that was used to sign data with content type `text/plain` and the signature.
#### Arguments #### Arguments
- data [data]: data that was signed - data [data]: data that was signed
- signature [data]: the signature to verify - signature [data]: the signature to verify
...@@ -400,6 +506,7 @@ Response ...@@ -400,6 +506,7 @@ Response
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "account_ecRecover", "method": "account_ecRecover",
"params": [ "params": [
"data/plain",
"0xaabbccdd", "0xaabbccdd",
"0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
] ]
...@@ -413,7 +520,6 @@ Response ...@@ -413,7 +520,6 @@ Response
"jsonrpc": "2.0", "jsonrpc": "2.0",
"result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db" "result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db"
} }
``` ```
### account_import ### account_import
...@@ -458,7 +564,7 @@ Response ...@@ -458,7 +564,7 @@ Response
}, },
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
"version": 3 "version": 3
}, }
] ]
} }
``` ```
......
### Changelog for external API ### Changelog for external API
#### 5.0.0
* The external `account_EcRecover`-method was reimplemented.
* The external method `account_sign(address, data)` was replaced with `account_signData(contentType, address, data)`.
The addition of `contentType` makes it possible to use the method for different types of objects, such as:
* signing data with an intended validator (not yet implemented)
* signing clique headers,
* signing plain personal messages,
* The external method `account_signTypedData` implements [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) and makes it possible to sign typed data.
#### 4.0.0 #### 4.0.0
* The external `account_Ecrecover`-method was removed. * The external `account_Ecrecover`-method was removed.
......
### Changelog for internal API (ui-api) ### Changelog for internal API (ui-api)
### 3.1.0
* Add `ContentType string` to `SignDataRequest` to accommodate the latest EIP-191 and EIP-712 implementations.
### 3.0.0 ### 3.0.0
* Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup * Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup
......
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
"os/signal" "os/signal"
"os/user" "os/user"
...@@ -39,9 +40,11 @@ import ( ...@@ -39,9 +40,11 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core" "github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/rules" "github.com/ethereum/go-ethereum/signer/rules"
...@@ -623,10 +626,40 @@ func testExternalUI(api *core.SignerAPI) { ...@@ -623,10 +626,40 @@ func testExternalUI(api *core.SignerAPI) {
} }
var err error var err error
cliqueHeader := types.Header{
common.HexToHash("0000H45H"),
common.HexToHash("0000H45H"),
common.HexToAddress("0000H45H"),
common.HexToHash("0000H00H"),
common.HexToHash("0000H45H"),
common.HexToHash("0000H45H"),
types.Bloom{},
big.NewInt(1337),
big.NewInt(1337),
1338,
1338,
big.NewInt(1338),
[]byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"),
common.HexToHash("0x0000H45H"),
types.BlockNonce{},
}
cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
if err != nil {
utils.Fatalf("Should not error: %v", err)
}
addr, err := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
if err != nil {
utils.Fatalf("Should not error: %v", err)
}
_, err = api.SignData(ctx, "application/clique", *addr, cliqueRlp)
checkErr("SignData", err)
_, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil)
checkErr("SignTransaction", err) checkErr("SignTransaction", err)
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
checkErr("Sign", err) checkErr("SignData", err)
//_, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{})
//checkErr("SignTypedData", err)
_, err = api.List(ctx) _, err = api.List(ctx)
checkErr("List", err) checkErr("List", err)
_, err = api.New(ctx) _, err = api.New(ctx)
...@@ -646,7 +679,6 @@ func testExternalUI(api *core.SignerAPI) { ...@@ -646,7 +679,6 @@ func testExternalUI(api *core.SignerAPI) {
} else { } else {
log.Info("No errors") log.Info("No errors")
} }
} }
// getPassPhrase retrieves the password associated with clef, either fetched // getPassPhrase retrieves the password associated with clef, either fetched
......
...@@ -616,7 +616,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c ...@@ -616,7 +616,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, results c
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
} }
// Sign all the things! // Sign all the things!
sighash, err := signFn(accounts.Account{Address: signer}, "application/x-clique-header", CliqueRLP(header)) sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeClique, CliqueRLP(header))
if err != nil { if err != nil {
return err return err
} }
......
...@@ -17,17 +17,16 @@ ...@@ -17,17 +17,16 @@
package core package core
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"regexp"
"strings" "strings"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"bytes"
"os"
"regexp"
) )
type decodedArgument struct { type decodedArgument struct {
......
...@@ -18,12 +18,11 @@ package core ...@@ -18,12 +18,11 @@ package core
import ( import (
"fmt" "fmt"
"strings"
"testing"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"reflect" "reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
......
...@@ -30,7 +30,6 @@ import ( ...@@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet"
"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/crypto"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
...@@ -40,9 +39,9 @@ const ( ...@@ -40,9 +39,9 @@ const (
// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive // numberOfAccountsToDerive For hardware wallets, the number of accounts to derive
numberOfAccountsToDerive = 10 numberOfAccountsToDerive = 10
// ExternalAPIVersion -- see extapi_changelog.md // ExternalAPIVersion -- see extapi_changelog.md
ExternalAPIVersion = "4.0.0" ExternalAPIVersion = "5.0.0"
// InternalAPIVersion -- see intapi_changelog.md // InternalAPIVersion -- see intapi_changelog.md
InternalAPIVersion = "3.0.0" InternalAPIVersion = "3.1.0"
) )
// ExternalAPI defines the external API through which signing requests are made. // ExternalAPI defines the external API through which signing requests are made.
...@@ -53,8 +52,12 @@ type ExternalAPI interface { ...@@ -53,8 +52,12 @@ type ExternalAPI interface {
New(ctx context.Context) (accounts.Account, error) New(ctx context.Context) (accounts.Account, error)
// SignTransaction request to sign the specified transaction // SignTransaction request to sign the specified transaction
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// Sign - request to sign the given data (plus prefix) // SignData - request to sign the given data (plus prefix)
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (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(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error)
// EcRecover - recover public key from given message and signature
EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error)
// Export - request to export an account // Export - request to export an account
Export(ctx context.Context, addr common.Address) (json.RawMessage, error) Export(ctx context.Context, addr common.Address) (json.RawMessage, error)
// Import - request to import an account // Import - request to import an account
...@@ -177,11 +180,12 @@ type ( ...@@ -177,11 +180,12 @@ type (
NewPassword string `json:"new_password"` NewPassword string `json:"new_password"`
} }
SignDataRequest struct { SignDataRequest struct {
Address common.MixedcaseAddress `json:"address"` ContentType string `json:"content_type"`
Rawdata hexutil.Bytes `json:"raw_data"` Address common.MixedcaseAddress `json:"address"`
Message string `json:"message"` Rawdata []byte `json:"raw_data"`
Hash hexutil.Bytes `json:"hash"` Message []*NameValueType `json:"message"`
Meta Metadata `json:"meta"` Hash hexutil.Bytes `json:"hash"`
Meta Metadata `json:"meta"`
} }
SignDataResponse struct { SignDataResponse struct {
Approved bool `json:"approved"` Approved bool `json:"approved"`
...@@ -517,56 +521,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth ...@@ -517,56 +521,6 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
} }
// Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
//
// 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.
//
// The key used to calculate the signature is decrypted with the given password.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
sighash, msg := SignHash(data)
// We make the request prior to looking up if we actually have the account, to prevent
// account-enumeration via the API
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)}
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
}
// Assemble sign the data with the wallet
signature, err := wallet.SignTextWithPassphrase(account, res.Password, data)
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
}
// SignHash 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("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func SignHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
return crypto.Keccak256([]byte(msg)), msg
}
// Export returns encrypted private key associated with the given address in web3 keystore format. // 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) { func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)})
......
...@@ -244,45 +244,6 @@ func TestNewAcc(t *testing.T) { ...@@ -244,45 +244,6 @@ func TestNewAcc(t *testing.T) {
} }
} }
func TestSignData(t *testing.T) {
api, control := setup(t)
//Create two accounts
createAccount(control, api, t)
createAccount(control, api, t)
control <- "1"
list, err := api.List(context.Background())
if err != nil {
t.Fatal(err)
}
a := common.NewMixedcaseAddress(list[0])
control <- "Y"
control <- "wrongpassword"
h, err := api.Sign(context.Background(), a, []byte("EHLO world"))
if h != nil {
t.Errorf("Expected nil-data, got %x", h)
}
if err != keystore.ErrDecrypt {
t.Errorf("Expected ErrLocked! %v", err)
}
control <- "No way"
h, err = api.Sign(context.Background(), a, []byte("EHLO world"))
if h != nil {
t.Errorf("Expected nil-data, got %x", h)
}
if err != ErrRequestDenied {
t.Errorf("Expected ErrRequestDenied! %v", err)
}
control <- "Y"
control <- "a_long_password"
h, err = api.Sign(context.Background(), a, []byte("EHLO world"))
if err != nil {
t.Fatal(err)
}
if h == nil || len(h) != 65 {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(h))
}
}
func mkTestTx(from common.MixedcaseAddress) SendTxArgs { func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
gas := hexutil.Uint64(21000) gas := hexutil.Uint64(21000)
......
...@@ -18,7 +18,6 @@ package core ...@@ -18,7 +18,6 @@ package core
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
...@@ -63,11 +62,27 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth ...@@ -63,11 +62,27 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth
return res, e return res, e
} }
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) {
l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(), l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", common.Bytes2Hex(data)) "addr", addr.String(), "data", data, "content-type", contentType)
b, e := l.api.Sign(ctx, addr, data) b, e := l.api.SignData(ctx, contentType, addr, data)
l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e) l.log.Info("SignData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e
}
func (l *AuditLogger) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, data TypedData) (hexutil.Bytes, error) {
l.log.Info("SignTypedData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", data)
b, e := l.api.SignTypedData(ctx, addr, data)
l.log.Info("SignTypedData", "type", "response", "data", common.Bytes2Hex(b), "error", e)
return b, e
}
func (l *AuditLogger) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) {
l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"data", common.Bytes2Hex(data), "sig", common.Bytes2Hex(sig))
b, e := l.api.EcRecover(ctx, data, sig)
l.log.Info("EcRecover", "type", "response", "address", b.String(), "error", e)
return b, e return b, e
} }
......
...@@ -21,7 +21,6 @@ import ( ...@@ -21,7 +21,6 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"sync" "sync"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
...@@ -165,8 +164,12 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp ...@@ -165,8 +164,12 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("-------- Sign data request--------------\n")
fmt.Printf("Account: %s\n", request.Address.String()) fmt.Printf("Account: %s\n", request.Address.String())
fmt.Printf("message: \n%q\n", request.Message) fmt.Printf("message:\n")
fmt.Printf("raw data: \n%v\n", request.Rawdata) for _, nvt := range request.Message {
fmt.Printf("%v\n", nvt.Pprint(1))
}
//fmt.Printf("message: \n%v\n", request.Message)
fmt.Printf("raw data: \n%q\n", request.Rawdata)
fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("message hash: %v\n", request.Hash)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(request.Meta) showMetadata(request.Meta)
......
This diff is collapsed.
This diff is collapsed.
...@@ -19,9 +19,8 @@ package core ...@@ -19,9 +19,8 @@ package core
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"math/big" "math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
......
...@@ -624,7 +624,7 @@ func TestSignData(t *testing.T) { ...@@ -624,7 +624,7 @@ func TestSignData(t *testing.T) {
function ApproveSignData(r){ function ApproveSignData(r){
if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa") if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa")
{ {
if(r.message.indexOf("bazonk") >= 0){ if(r.message[0].value.indexOf("bazonk") >= 0){
return "Approve" return "Approve"
} }
return "Reject" return "Reject"
...@@ -636,18 +636,25 @@ function ApproveSignData(r){ ...@@ -636,18 +636,25 @@ function ApproveSignData(r){
t.Errorf("Couldn't create evaluator %v", err) t.Errorf("Couldn't create evaluator %v", err)
return return
} }
message := []byte("baz bazonk foo") message := "baz bazonk foo"
hash, msg := core.SignHash(message) hash, rawdata := accounts.TextAndHash([]byte(message))
raw := hexutil.Bytes(message)
addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa")
fmt.Printf("address %v %v\n", addr.String(), addr.Original()) fmt.Printf("address %v %v\n", addr.String(), addr.Original())
nvt := []*core.NameValueType{
{
Name: "message",
Typ: "text/plain",
Value: message,
},
}
resp, err := r.ApproveSignData(&core.SignDataRequest{ resp, err := r.ApproveSignData(&core.SignDataRequest{
Address: *addr, Address: *addr,
Message: msg, Message: nvt,
Hash: hash, Hash: hash,
Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
Rawdata: raw, Rawdata: []byte(rawdata),
}) })
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v", err) t.Fatalf("Unexpected error %v", err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment