diff --git a/signer/core/api.go b/signer/core/api.go
index 244767acaf..c0ed6b6a15 100644
--- a/signer/core/api.go
+++ b/signer/core/api.go
@@ -14,14 +14,16 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
+// +build !js
+
package core
import (
"context"
- "encoding/json"
"errors"
"fmt"
"math/big"
+ "mime"
"os"
"reflect"
@@ -31,7 +33,9 @@ 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/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/signer/storage"
@@ -116,15 +120,6 @@ type SignerAPI struct {
credentials storage.Storage
}
-// Metadata about a request
-type Metadata struct {
- Remote string `json:"remote"`
- Local string `json:"local"`
- Scheme string `json:"scheme"`
- UserAgent string `json:"User-Agent"`
- Origin string `json:"Origin"`
-}
-
func StartClefAccountManager(ksLocation string, nousb, lightKDF bool, scpath string) *accounts.Manager {
var (
backends []accounts.Backend
@@ -206,15 +201,6 @@ func MetadataFromContext(ctx context.Context) Metadata {
return m
}
-// String implements Stringer interface
-func (m Metadata) String() string {
- s, err := json.Marshal(m)
- if err == nil {
- return string(s)
- }
- return err.Error()
-}
-
// types for the requests/response types between signer and UI
type (
// SignTxRequest contains info about a Transaction to sign
@@ -229,14 +215,6 @@ type (
Transaction SendTxArgs `json:"transaction"`
Approved bool `json:"approved"`
}
- SignDataRequest struct {
- ContentType string `json:"content_type"`
- Address common.MixedcaseAddress `json:"address"`
- Rawdata []byte `json:"raw_data"`
- Messages []*NameValueType `json:"messages"`
- Hash hexutil.Bytes `json:"hash"`
- Meta Metadata `json:"meta"`
- }
SignDataResponse struct {
Approved bool `json:"approved"`
}
@@ -286,6 +264,226 @@ func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAP
}
return signer
}
+
+// sign receives a request and produces a signature
+//
+// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
+// where the V value will be 27 or 28 for legacy reasons, if legacyV==true.
+func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) {
+ // We make the request prior to looking up if we actually have the account, to prevent
+ // account-enumeration via the API
+ res, err := api.UI.ApproveSignData(req)
+ if err != nil {
+ return nil, err
+ }
+ if !res.Approved {
+ return nil, ErrRequestDenied
+ }
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: addr.Address()}
+ wallet, err := api.am.Find(account)
+ if err != nil {
+ return nil, err
+ }
+ pw, err := api.lookupOrQueryPassword(account.Address,
+ "Password for signing",
+ fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex()))
+ if err != nil {
+ return nil, err
+ }
+ // Sign the data with the wallet
+ signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata)
+ if err != nil {
+ return nil, err
+ }
+ if legacyV {
+ signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
+ }
+ return signature, nil
+}
+
+// SignData signs the hash of the provided data, but does so differently
+// depending on the content-type specified.
+//
+// Different types of validation occur.
+func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) {
+ var req, transformV, err = api.determineSignatureFormat(ctx, contentType, addr, data)
+ if err != nil {
+ return nil, err
+ }
+ signature, err := api.sign(addr, req, transformV)
+ if err != nil {
+ api.UI.ShowError(err.Error())
+ return nil, err
+ }
+ return signature, nil
+}
+
+// determineSignatureFormat determines which signature method should be used based upon the mime type
+// In the cases where it matters ensure that the charset is handled. The charset
+// resides in the 'params' returned as the second returnvalue from mime.ParseMediaType
+// charset, ok := params["charset"]
+// As it is now, we accept any charset and just treat it as 'raw'.
+// This method returns the mimetype for signing along with the request
+func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, bool, error) {
+ var (
+ req *SignDataRequest
+ useEthereumV = true // Default to use V = 27 or 28, the legacy Ethereum format
+ )
+ mediaType, _, err := mime.ParseMediaType(contentType)
+ if err != nil {
+ return nil, useEthereumV, err
+ }
+
+ switch mediaType {
+ case IntendedValidator.Mime:
+ // Data with an intended validator
+ validatorData, err := UnmarshalValidatorData(data)
+ if err != nil {
+ return nil, useEthereumV, err
+ }
+ sighash, msg := SignTextValidator(validatorData)
+ messages := []*NameValueType{
+ {
+ Name: "This is a request to sign data intended for a particular validator (see EIP 191 version 0)",
+ Typ: "description",
+ Value: "",
+ },
+ {
+ Name: "Intended validator address",
+ Typ: "address",
+ Value: validatorData.Address.String(),
+ },
+ {
+ Name: "Application-specific data",
+ Typ: "hexdata",
+ Value: validatorData.Message,
+ },
+ {
+ Name: "Full message for signing",
+ Typ: "hexdata",
+ Value: fmt.Sprintf("0x%x", msg),
+ },
+ }
+ req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
+ case ApplicationClique.Mime:
+ // Clique is the Ethereum PoA standard
+ stringData, ok := data.(string)
+ if !ok {
+ return nil, useEthereumV, fmt.Errorf("input for %v must be an hex-encoded string", ApplicationClique.Mime)
+ }
+ cliqueData, err := hexutil.Decode(stringData)
+ if err != nil {
+ return nil, useEthereumV, err
+ }
+ header := &types.Header{}
+ if err := rlp.DecodeBytes(cliqueData, header); err != nil {
+ return nil, useEthereumV, err
+ }
+ // The incoming clique header is already truncated, sent to us with a extradata already shortened
+ if len(header.Extra) < 65 {
+ // Need to add it back, to get a suitable length for hashing
+ newExtra := make([]byte, len(header.Extra)+65)
+ copy(newExtra, header.Extra)
+ header.Extra = newExtra
+ }
+ // Get back the rlp data, encoded by us
+ sighash, cliqueRlp, err := cliqueHeaderHashAndRlp(header)
+ if err != nil {
+ return nil, useEthereumV, err
+ }
+ messages := []*NameValueType{
+ {
+ Name: "Clique header",
+ Typ: "clique",
+ Value: fmt.Sprintf("clique header %d [0x%x]", header.Number, header.Hash()),
+ },
+ }
+ // Clique uses V on the form 0 or 1
+ useEthereumV = false
+ req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash}
+ default: // also case TextPlain.Mime:
+ // Calculates an Ethereum ECDSA signature for:
+ // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}")
+ // We expect it to be a string
+ if stringData, ok := data.(string); !ok {
+ return nil, useEthereumV, fmt.Errorf("input for text/plain must be an hex-encoded string")
+ } else {
+ if textData, err := hexutil.Decode(stringData); err != nil {
+ return nil, useEthereumV, err
+ } else {
+ sighash, msg := accounts.TextAndHash(textData)
+ messages := []*NameValueType{
+ {
+ Name: "message",
+ Typ: accounts.MimetypeTextPlain,
+ Value: msg,
+ },
+ }
+ req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
+ }
+ }
+ }
+ req.Address = addr
+ req.Meta = MetadataFromContext(ctx)
+ return req, useEthereumV, nil
+}
+
+// SignTypedData signs EIP-712 conformant typed data
+// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
+func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) {
+ domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
+ if err != nil {
+ return nil, err
+ }
+ typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
+ if err != nil {
+ return nil, err
+ }
+ rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
+ sighash := crypto.Keccak256(rawData)
+ messages, err := typedData.Format()
+ if err != nil {
+ return nil, err
+ }
+ req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Messages: messages, Hash: sighash}
+ signature, err := api.sign(addr, req, true)
+ if err != nil {
+ api.UI.ShowError(err.Error())
+ return nil, err
+ }
+ return signature, nil
+}
+
+// EcRecover recovers the address associated with the given sig.
+// Only compatible with `text/plain`
+func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) {
+ // Returns the address for the Account that was used to create the signature.
+ //
+ // Note, this function is compatible with eth_sign and personal_sign. As such it recovers
+ // the address of:
+ // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}")
+ // addr = ecrecover(hash, signature)
+ //
+ // Note, the signature must conform to the secp256k1 curve R, S and V values, where
+ // the V value must be be 27 or 28 for legacy reasons.
+ //
+ // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
+ if len(sig) != 65 {
+ return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
+ }
+ if sig[64] != 27 && sig[64] != 28 {
+ return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
+ }
+ sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
+ hash := accounts.TextHash(data)
+ rpk, err := crypto.SigToPub(hash, sig)
+ if err != nil {
+ return common.Address{}, err
+ }
+ return crypto.PubkeyToAddress(*rpk), nil
+}
+
func (api *SignerAPI) openTrezor(url accounts.URL) {
resp, err := api.UI.OnInputRequired(UserInputRequest{
Prompt: "Pin required to open Trezor wallet\n" +
diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go
index 1092e7a923..1e90576096 100644
--- a/signer/core/auditlog.go
+++ b/signer/core/auditlog.go
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
+// +build !js
+
package core
import (
diff --git a/signer/core/cliui.go b/signer/core/cliui.go
index 1502238bf7..5333e013cb 100644
--- a/signer/core/cliui.go
+++ b/signer/core/cliui.go
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
+// +build !js
+
package core
import (
diff --git a/signer/core/signed_data.go b/signer/core/signed_data.go
index f512be7cea..8639bed4f0 100644
--- a/signer/core/signed_data.go
+++ b/signer/core/signed_data.go
@@ -18,11 +18,10 @@ package core
import (
"bytes"
- "context"
+ "encoding/json"
"errors"
"fmt"
"math/big"
- "mime"
"reflect"
"regexp"
"sort"
@@ -38,7 +37,6 @@ import (
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/rlp"
)
type SigFormat struct {
@@ -122,168 +120,31 @@ type TypedDataDomain struct {
var typedDataReferenceTypeRegexp = regexp.MustCompile(`^[A-Z](\w*)(\[\])?$`)
-// sign receives a request and produces a signature
-//
-// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
-// where the V value will be 27 or 28 for legacy reasons, if legacyV==true.
-func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, legacyV bool) (hexutil.Bytes, error) {
- // We make the request prior to looking up if we actually have the account, to prevent
- // account-enumeration via the API
- res, err := api.UI.ApproveSignData(req)
- if err != nil {
- return nil, err
- }
- if !res.Approved {
- return nil, ErrRequestDenied
- }
- // Look up the wallet containing the requested signer
- account := accounts.Account{Address: addr.Address()}
- wallet, err := api.am.Find(account)
- if err != nil {
- return nil, err
- }
- pw, err := api.lookupOrQueryPassword(account.Address,
- "Password for signing",
- fmt.Sprintf("Please enter password for signing data with account %s", account.Address.Hex()))
- if err != nil {
- return nil, err
- }
- // Sign the data with the wallet
- signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata)
- if err != nil {
- return nil, err
- }
- if legacyV {
- signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
- }
- return signature, nil
+// Metadata about a request
+type Metadata struct {
+ Remote string `json:"remote"`
+ Local string `json:"local"`
+ Scheme string `json:"scheme"`
+ UserAgent string `json:"User-Agent"`
+ Origin string `json:"Origin"`
}
-// SignData signs the hash of the provided data, but does so differently
-// depending on the content-type specified.
-//
-// Different types of validation occur.
-func (api *SignerAPI) SignData(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (hexutil.Bytes, error) {
- var req, transformV, err = api.determineSignatureFormat(ctx, contentType, addr, data)
- if err != nil {
- return nil, err
+// String implements Stringer interface
+func (m Metadata) String() string {
+ s, err := json.Marshal(m)
+ if err == nil {
+ return string(s)
}
- signature, err := api.sign(addr, req, transformV)
- if err != nil {
- api.UI.ShowError(err.Error())
- return nil, err
- }
- return signature, nil
+ return err.Error()
}
-// determineSignatureFormat determines which signature method should be used based upon the mime type
-// In the cases where it matters ensure that the charset is handled. The charset
-// resides in the 'params' returned as the second returnvalue from mime.ParseMediaType
-// charset, ok := params["charset"]
-// As it is now, we accept any charset and just treat it as 'raw'.
-// This method returns the mimetype for signing along with the request
-func (api *SignerAPI) determineSignatureFormat(ctx context.Context, contentType string, addr common.MixedcaseAddress, data interface{}) (*SignDataRequest, bool, error) {
- var (
- req *SignDataRequest
- useEthereumV = true // Default to use V = 27 or 28, the legacy Ethereum format
- )
- mediaType, _, err := mime.ParseMediaType(contentType)
- if err != nil {
- return nil, useEthereumV, err
- }
-
- switch mediaType {
- case IntendedValidator.Mime:
- // Data with an intended validator
- validatorData, err := UnmarshalValidatorData(data)
- if err != nil {
- return nil, useEthereumV, err
- }
- sighash, msg := SignTextValidator(validatorData)
- messages := []*NameValueType{
- {
- Name: "This is a request to sign data intended for a particular validator (see EIP 191 version 0)",
- Typ: "description",
- Value: "",
- },
- {
- Name: "Intended validator address",
- Typ: "address",
- Value: validatorData.Address.String(),
- },
- {
- Name: "Application-specific data",
- Typ: "hexdata",
- Value: validatorData.Message,
- },
- {
- Name: "Full message for signing",
- Typ: "hexdata",
- Value: fmt.Sprintf("0x%x", msg),
- },
- }
- req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
- case ApplicationClique.Mime:
- // Clique is the Ethereum PoA standard
- stringData, ok := data.(string)
- if !ok {
- return nil, useEthereumV, fmt.Errorf("input for %v must be an hex-encoded string", ApplicationClique.Mime)
- }
- cliqueData, err := hexutil.Decode(stringData)
- if err != nil {
- return nil, useEthereumV, err
- }
- header := &types.Header{}
- if err := rlp.DecodeBytes(cliqueData, header); err != nil {
- return nil, useEthereumV, err
- }
- // The incoming clique header is already truncated, sent to us with a extradata already shortened
- if len(header.Extra) < 65 {
- // Need to add it back, to get a suitable length for hashing
- newExtra := make([]byte, len(header.Extra)+65)
- copy(newExtra, header.Extra)
- header.Extra = newExtra
- }
- // Get back the rlp data, encoded by us
- sighash, cliqueRlp, err := cliqueHeaderHashAndRlp(header)
- if err != nil {
- return nil, useEthereumV, err
- }
- messages := []*NameValueType{
- {
- Name: "Clique header",
- Typ: "clique",
- Value: fmt.Sprintf("clique header %d [0x%x]", header.Number, header.Hash()),
- },
- }
- // Clique uses V on the form 0 or 1
- useEthereumV = false
- req = &SignDataRequest{ContentType: mediaType, Rawdata: cliqueRlp, Messages: messages, Hash: sighash}
- default: // also case TextPlain.Mime:
- // Calculates an Ethereum ECDSA signature for:
- // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}")
- // We expect it to be a string
- if stringData, ok := data.(string); !ok {
- return nil, useEthereumV, fmt.Errorf("input for text/plain must be an hex-encoded string")
- } else {
- if textData, err := hexutil.Decode(stringData); err != nil {
- return nil, useEthereumV, err
- } else {
- sighash, msg := accounts.TextAndHash(textData)
- messages := []*NameValueType{
- {
- Name: "message",
- Typ: accounts.MimetypeTextPlain,
- Value: msg,
- },
- }
- req = &SignDataRequest{ContentType: mediaType, Rawdata: []byte(msg), Messages: messages, Hash: sighash}
- }
- }
- }
- req.Address = addr
- req.Meta = MetadataFromContext(ctx)
- return req, useEthereumV, nil
+type SignDataRequest struct {
+ ContentType string `json:"content_type"`
+ Address common.MixedcaseAddress `json:"address"`
+ Rawdata []byte `json:"raw_data"`
+ Messages []*NameValueType `json:"messages"`
+ Hash hexutil.Bytes `json:"hash"`
+ Meta Metadata `json:"meta"`
}
// SignTextWithValidator signs the given message which can be further recovered
@@ -311,32 +172,6 @@ func cliqueHeaderHashAndRlp(header *types.Header) (hash, rlp []byte, err error)
return hash, rlp, err
}
-// SignTypedData signs EIP-712 conformant typed data
-// hash = keccak256("\x19${byteVersion}${domainSeparator}${hashStruct(message)}")
-func (api *SignerAPI) SignTypedData(ctx context.Context, addr common.MixedcaseAddress, typedData TypedData) (hexutil.Bytes, error) {
- domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
- if err != nil {
- return nil, err
- }
- typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
- if err != nil {
- return nil, err
- }
- rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
- sighash := crypto.Keccak256(rawData)
- messages, err := typedData.Format()
- if err != nil {
- return nil, err
- }
- req := &SignDataRequest{ContentType: DataTyped.Mime, Rawdata: rawData, Messages: messages, Hash: sighash}
- signature, err := api.sign(addr, req, true)
- if err != nil {
- api.UI.ShowError(err.Error())
- return nil, err
- }
- return signature, 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)
@@ -599,35 +434,6 @@ 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.
-// Only compatible with `text/plain`
-func (api *SignerAPI) EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) {
- // Returns the address for the Account that was used to create the signature.
- //
- // Note, this function is compatible with eth_sign and personal_sign. As such it recovers
- // the address of:
- // hash = keccak256("\x19${byteVersion}Ethereum Signed Message:\n${message length}${message}")
- // addr = ecrecover(hash, signature)
- //
- // Note, the signature must conform to the secp256k1 curve R, S and V values, where
- // the V value must be be 27 or 28 for legacy reasons.
- //
- // https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
- if len(sig) != 65 {
- return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
- }
- if sig[64] != 27 && sig[64] != 28 {
- return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
- }
- sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
- hash := accounts.TextHash(data)
- rpk, err := crypto.SigToPub(hash, sig)
- if err != nil {
- return common.Address{}, err
- }
- return crypto.PubkeyToAddress(*rpk), nil
-}
-
// UnmarshalValidatorData converts the bytes input to typed data
func UnmarshalValidatorData(data interface{}) (ValidatorData, error) {
raw, ok := data.(map[string]interface{})
diff --git a/signer/core/stdioui.go b/signer/core/stdioui.go
index 9ffe1398d4..f5952daf22 100644
--- a/signer/core/stdioui.go
+++ b/signer/core/stdioui.go
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
+// +build !js
+
package core
import (
diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go
index 7c2d233f89..3f3473efa9 100644
--- a/signer/core/uiapi.go
+++ b/signer/core/uiapi.go
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
+// +build !js
+
package core
import (