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 {
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
// accounts (derived from the same seed).
type Wallet interface {
......@@ -101,6 +108,12 @@ type Wallet interface {
// the account in a keystore).
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
// by the Ethereum prefix scheme
// It looks up the account specified either solely via its address contained within,
......@@ -114,6 +127,9 @@ type Wallet interface {
// the account in a keystore).
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.
//
// It looks up the account specified either solely via its address contained within,
......@@ -127,18 +143,7 @@ type Wallet interface {
// the account in a keystore).
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
// SignTextWithPassphrase requests the wallet to sign the given text 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.
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 is identical to SignTx, but also takes a password
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
}
......@@ -170,9 +175,22 @@ type Backend interface {
//
// This gives context to the signed message and prevents signing of transactions.
func TextHash(data []byte) []byte {
hash := sha3.NewLegacyKeccak256()
fmt.Fprintf(hash, "\x19Ethereum Signed Message:\n%d%s", len(data), data)
return hash.Sum(nil)
hash, _ := TextAndHash(data)
return hash
}
// 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
......
......@@ -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) {
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) {
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) {
......@@ -201,7 +204,7 @@ func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
func (api *ExternalSigner) signCliqueBlock(a common.Address, rlpBlock hexutil.Bytes) (hexutil.Bytes, error) {
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
}
if sig[64] != 27 && sig[64] != 28 {
......
......@@ -97,21 +97,18 @@ func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, dat
return w.signHash(account, crypto.Keccak256(data))
}
func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
return w.signHash(account, 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) {
// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed
func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, 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)
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
......@@ -125,6 +122,19 @@ func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passph
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
// 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) {
......
......@@ -507,6 +507,13 @@ func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte
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) {
return w.signHash(account, accounts.TextHash(text))
}
......
......@@ -189,7 +189,9 @@ None
"method": "account_new",
"params": []
}
```
Response
```
{
"id": 0,
"jsonrpc": "2.0",
......@@ -222,7 +224,9 @@ None
"jsonrpc": "2.0",
"method": "account_list"
}
```
Response
```
{
"id": 1,
"jsonrpc": "2.0",
......@@ -285,8 +289,8 @@ Response
```json
{
"id": 2,
"jsonrpc": "2.0",
"id": 67,
"error": {
"code": -32000,
"message": "Request denied"
......@@ -298,6 +302,7 @@ Response
```json
{
"id": 67,
"jsonrpc": "2.0",
"method": "account_signTransaction",
"params": [
......@@ -311,8 +316,7 @@ Response
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"
},
"safeSend(address)"
],
"id": 67
]
}
```
Response
......@@ -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"}}}
```
### account_sign
### account_signData
#### Sign data
Signs a chunk of data and returns the calculated signature.
#### 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
- data [data]: data to sign
- data [object]: data to sign
#### Result
- calculated signature [data]
......@@ -364,8 +371,9 @@ Bash example:
{
"id": 3,
"jsonrpc": "2.0",
"method": "account_sign",
"method": "account_signData",
"params": [
"data/plain",
"0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
"0xaabbccdd"
]
......@@ -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
#### Recover address
Derive the address from the account that was used to sign data from the data and signature.
#### Sign data
Derive the address from the account that was used to sign data with content type `text/plain` and the signature.
#### Arguments
- data [data]: data that was signed
- signature [data]: the signature to verify
......@@ -400,6 +506,7 @@ Response
"jsonrpc": "2.0",
"method": "account_ecRecover",
"params": [
"data/plain",
"0xaabbccdd",
"0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
]
......@@ -413,7 +520,6 @@ Response
"jsonrpc": "2.0",
"result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db"
}
```
### account_import
......@@ -458,7 +564,7 @@ Response
},
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
"version": 3
},
}
]
}
```
......
### 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
* The external `account_Ecrecover`-method was removed.
......
### 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
* Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup
......
......@@ -28,6 +28,7 @@ import (
"fmt"
"io"
"io/ioutil"
"math/big"
"os"
"os/signal"
"os/user"
......@@ -39,9 +40,11 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/rules"
......@@ -623,10 +626,40 @@ func testExternalUI(api *core.SignerAPI) {
}
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)
checkErr("SignTransaction", err)
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
checkErr("Sign", err)
_, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
checkErr("SignData", err)
//_, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{})
//checkErr("SignTypedData", err)
_, err = api.List(ctx)
checkErr("List", err)
_, err = api.New(ctx)
......@@ -646,7 +679,6 @@ func testExternalUI(api *core.SignerAPI) {
} else {
log.Info("No errors")
}
}
// 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
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
}
// 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 {
return err
}
......
......@@ -17,17 +17,16 @@
package core
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"bytes"
"os"
"regexp"
)
type decodedArgument struct {
......
......@@ -18,12 +18,11 @@ package core
import (
"fmt"
"strings"
"testing"
"io/ioutil"
"math/big"
"reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
......
......@@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/usbwallet"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
......@@ -40,9 +39,9 @@ const (
// numberOfAccountsToDerive For hardware wallets, the number of accounts to derive
numberOfAccountsToDerive = 10
// ExternalAPIVersion -- see extapi_changelog.md
ExternalAPIVersion = "4.0.0"
ExternalAPIVersion = "5.0.0"
// InternalAPIVersion -- see intapi_changelog.md
InternalAPIVersion = "3.0.0"
InternalAPIVersion = "3.1.0"
)
// ExternalAPI defines the external API through which signing requests are made.
......@@ -53,8 +52,12 @@ type ExternalAPI interface {
New(ctx context.Context) (accounts.Account, error)
// SignTransaction request to sign the specified transaction
SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// Sign - request to sign the given data (plus prefix)
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
// SignData - request to sign the given data (plus prefix)
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(ctx context.Context, addr common.Address) (json.RawMessage, error)
// Import - request to import an account
......@@ -177,11 +180,12 @@ type (
NewPassword string `json:"new_password"`
}
SignDataRequest struct {
Address common.MixedcaseAddress `json:"address"`
Rawdata hexutil.Bytes `json:"raw_data"`
Message string `json:"message"`
Hash hexutil.Bytes `json:"hash"`
Meta Metadata `json:"meta"`
ContentType string `json:"content_type"`
Address common.MixedcaseAddress `json:"address"`
Rawdata []byte `json:"raw_data"`
Message []*NameValueType `json:"message"`
Hash hexutil.Bytes `json:"hash"`
Meta Metadata `json:"meta"`
}
SignDataResponse struct {
Approved bool `json:"approved"`
......@@ -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.
func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)})
......
......@@ -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 {
to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
gas := hexutil.Uint64(21000)
......
......@@ -18,7 +18,6 @@ package core
import (
"context"
"encoding/json"
"github.com/ethereum/go-ethereum/accounts"
......@@ -63,11 +62,27 @@ func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, meth
return res, e
}
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {
l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", common.Bytes2Hex(data))
b, e := l.api.Sign(ctx, addr, data)
l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e)
func (l *AuditLogger) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) {
l.log.Info("SignData", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"addr", addr.String(), "data", data, "content-type", contentType)
b, e := l.api.SignData(ctx, contentType, addr, data)
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
}
......
......@@ -21,7 +21,6 @@ import (
"fmt"
"os"
"strings"
"sync"
"github.com/davecgh/go-spew/spew"
......@@ -165,8 +164,12 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
fmt.Printf("-------- Sign data request--------------\n")
fmt.Printf("Account: %s\n", request.Address.String())
fmt.Printf("message: \n%q\n", request.Message)
fmt.Printf("raw data: \n%v\n", request.Rawdata)
fmt.Printf("message:\n")
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("-------------------------------------------\n")
showMetadata(request.Meta)
......
This diff is collapsed.
This diff is collapsed.
......@@ -19,9 +19,8 @@ package core
import (
"encoding/json"
"fmt"
"strings"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
......
......@@ -624,7 +624,7 @@ func TestSignData(t *testing.T) {
function ApproveSignData(r){
if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa")
{
if(r.message.indexOf("bazonk") >= 0){
if(r.message[0].value.indexOf("bazonk") >= 0){
return "Approve"
}
return "Reject"
......@@ -636,18 +636,25 @@ function ApproveSignData(r){
t.Errorf("Couldn't create evaluator %v", err)
return
}
message := []byte("baz bazonk foo")
hash, msg := core.SignHash(message)
raw := hexutil.Bytes(message)
message := "baz bazonk foo"
hash, rawdata := accounts.TextAndHash([]byte(message))
addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa")
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{
Address: *addr,
Message: msg,
Message: nvt,
Hash: hash,
Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"},
Rawdata: raw,
Rawdata: []byte(rawdata),
})
if err != nil {
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