signer: change the stdio jsonrpc to use legacy namespace conventions (#19047)
This PR will will break existing UIs, since it changes all calls like ApproveSignTransaction to be on the form ui_approveSignTransaction. This is to make it possible for the UI to reuse the json-rpc library from go-ethereum, which uses this convention. Also, this PR removes some unused structs, after import/export were removed from the external api (so no longer needs internal methods for approval) One more breaking change is introduced, removing passwords from the ApproveSignTxResponse and the likes. This makes the manual interface more like the rulebased interface, and integrates nicely with the credential storage. Thus, the way it worked before, it would be tempting for the UI to implement 'remember password' functionality. The way it is now, it will be easy instead to tell clef to store passwords and use them. If a pw is not found in the credential store, the user is prompted to provide the password.
This commit is contained in:
parent
eb199f1fc2
commit
5f94f8c7e7
@ -661,7 +661,7 @@ OBS! A slight deviation from `json` standard is in place: every request and resp
|
|||||||
Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make
|
Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make
|
||||||
things simpler for both parties.
|
things simpler for both parties.
|
||||||
|
|
||||||
### ApproveTx
|
### ApproveTx / `ui_approveTx`
|
||||||
|
|
||||||
Invoked when there's a transaction for approval.
|
Invoked when there's a transaction for approval.
|
||||||
|
|
||||||
@ -673,13 +673,13 @@ Here's a method invocation:
|
|||||||
|
|
||||||
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||||
```
|
```
|
||||||
|
Results in the following invocation on the UI:
|
||||||
```json
|
```json
|
||||||
|
|
||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "ApproveTx",
|
"method": "ui_approveTx",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"transaction": {
|
"transaction": {
|
||||||
@ -724,7 +724,7 @@ curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","me
|
|||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "ApproveTx",
|
"method": "ui_approveTx",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"transaction": {
|
"transaction": {
|
||||||
@ -767,7 +767,7 @@ One which has missing `to`, but with no `data`:
|
|||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"method": "ApproveTx",
|
"method": "ui_approveTx",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"transaction": {
|
"transaction": {
|
||||||
@ -796,33 +796,7 @@ One which has missing `to`, but with no `data`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### ApproveExport
|
### ApproveListing / `ui_approveListing`
|
||||||
|
|
||||||
Invoked when a request to export an account has been made.
|
|
||||||
|
|
||||||
#### Sample call
|
|
||||||
|
|
||||||
```json
|
|
||||||
|
|
||||||
{
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": 7,
|
|
||||||
"method": "ApproveExport",
|
|
||||||
"params": [
|
|
||||||
{
|
|
||||||
"address": "0x0000000000000000000000000000000000000000",
|
|
||||||
"meta": {
|
|
||||||
"remote": "signer binary",
|
|
||||||
"local": "main",
|
|
||||||
"scheme": "in-proc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### ApproveListing
|
|
||||||
|
|
||||||
Invoked when a request for account listing has been made.
|
Invoked when a request for account listing has been made.
|
||||||
|
|
||||||
@ -833,7 +807,7 @@ Invoked when a request for account listing has been made.
|
|||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"method": "ApproveListing",
|
"method": "ui_approveListing",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"accounts": [
|
"accounts": [
|
||||||
@ -860,7 +834,7 @@ Invoked when a request for account listing has been made.
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### ApproveSignData
|
### ApproveSignData / `ui_approveSignData`
|
||||||
|
|
||||||
#### Sample call
|
#### Sample call
|
||||||
|
|
||||||
@ -868,7 +842,7 @@ Invoked when a request for account listing has been made.
|
|||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"method": "ApproveSignData",
|
"method": "ui_approveSignData",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"address": "0x123409812340981234098123409812deadbeef42",
|
"address": "0x123409812340981234098123409812deadbeef42",
|
||||||
@ -886,7 +860,7 @@ Invoked when a request for account listing has been made.
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### ShowInfo
|
### ShowInfo / `ui_showInfo`
|
||||||
|
|
||||||
The UI should show the info to the user. Does not expect response.
|
The UI should show the info to the user. Does not expect response.
|
||||||
|
|
||||||
@ -896,7 +870,7 @@ The UI should show the info to the user. Does not expect response.
|
|||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"method": "ShowInfo",
|
"method": "ui_showInfo",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"text": "Tests completed"
|
"text": "Tests completed"
|
||||||
@ -906,7 +880,7 @@ The UI should show the info to the user. Does not expect response.
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### ShowError
|
### ShowError / `ui_showError`
|
||||||
|
|
||||||
The UI should show the info to the user. Does not expect response.
|
The UI should show the info to the user. Does not expect response.
|
||||||
|
|
||||||
@ -925,7 +899,7 @@ The UI should show the info to the user. Does not expect response.
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### OnApproved
|
### OnApprovedTx / `ui_onApprovedTx`
|
||||||
|
|
||||||
`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions.
|
`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions.
|
||||||
|
|
||||||
@ -933,7 +907,7 @@ When implementing rate-limited rules, this callback should be used.
|
|||||||
|
|
||||||
TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`.
|
TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`.
|
||||||
|
|
||||||
### OnSignerStartup
|
### OnSignerStartup / `ui_onSignerStartup`
|
||||||
|
|
||||||
This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external api,
|
This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external api,
|
||||||
in k/v-form.
|
in k/v-form.
|
||||||
@ -944,7 +918,7 @@ Example call:
|
|||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"method": "OnSignerStartup",
|
"method": "ui_onSignerStartup",
|
||||||
"params": [
|
"params": [
|
||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
|
@ -35,8 +35,7 @@ Response to SignDataRequest
|
|||||||
Example:
|
Example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"approved": true,
|
"approved": true
|
||||||
"Password": "apassword"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### SignDataResponse - deny
|
### SignDataResponse - deny
|
||||||
@ -46,8 +45,7 @@ Response to SignDataRequest
|
|||||||
Example:
|
Example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"approved": false,
|
"approved": false
|
||||||
"Password": ""
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### SignTxRequest
|
### SignTxRequest
|
||||||
@ -89,9 +87,9 @@ Example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### SignDataResponse - approve
|
### SignTxResponse - approve
|
||||||
|
|
||||||
Response to SignDataRequest. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction.
|
Response to request to sign a transaction. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```json
|
```json
|
||||||
@ -105,19 +103,26 @@ Example:
|
|||||||
"nonce": "0x4",
|
"nonce": "0x4",
|
||||||
"data": "0x04030201"
|
"data": "0x04030201"
|
||||||
},
|
},
|
||||||
"approved": true,
|
"approved": true
|
||||||
"password": "apassword"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### SignDataResponse - deny
|
### SignTxResponse - deny
|
||||||
|
|
||||||
Response to SignDataRequest. When denying a request, there's no need to provide the transaction in return
|
Response to SignTxRequest. When denying a request, there's no need to provide the transaction in return
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"approved": false,
|
"transaction": {
|
||||||
"Password": ""
|
"from": "0x",
|
||||||
|
"to": null,
|
||||||
|
"gas": "0x0",
|
||||||
|
"gasPrice": "0x0",
|
||||||
|
"value": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": null
|
||||||
|
},
|
||||||
|
"approved": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### OnApproved - SignTransactionResult
|
### OnApproved - SignTransactionResult
|
||||||
@ -164,7 +169,7 @@ Example:
|
|||||||
```
|
```
|
||||||
### UserInputResponse
|
### UserInputResponse
|
||||||
|
|
||||||
Response to SignDataRequest
|
Response to UserInputRequest
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```json
|
```json
|
||||||
@ -198,7 +203,7 @@ Example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### UserInputResponse
|
### ListResponse
|
||||||
|
|
||||||
Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not
|
Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not
|
||||||
|
|
||||||
|
@ -1,5 +1,40 @@
|
|||||||
### Changelog for internal API (ui-api)
|
### Changelog for internal API (ui-api)
|
||||||
|
|
||||||
|
### 6.0.0
|
||||||
|
|
||||||
|
Removed `password` from responses to operations which require them. This is for two reasons,
|
||||||
|
|
||||||
|
- Consistency between how rulesets operate and how manual processing works. A rule can `Approve` but require the actual password to be stored in the clef storage.
|
||||||
|
With this change, the same stored password can be used even if rulesets are not enabled, but storage is.
|
||||||
|
- It also removes the usability-shortcut that a UI might otherwise want to implement; remembering passwords. Since we now will not require the
|
||||||
|
password on every `Approve`, there's no need for the UI to cache it locally.
|
||||||
|
- In a future update, we'll likely add `clef_storePassword` to the internal API, so the user can store it via his UI (currently only CLI works).
|
||||||
|
|
||||||
|
Affected datatypes:
|
||||||
|
- `SignTxResponse`
|
||||||
|
- `SignDataResponse`
|
||||||
|
- `NewAccountResponse`
|
||||||
|
|
||||||
|
If `clef` requires a password, the `OnInputRequired` will be used to collect it.
|
||||||
|
|
||||||
|
|
||||||
|
### 5.0.0
|
||||||
|
|
||||||
|
Changed the namespace format to adhere to the legacy ethereum format: `name_methodName`. Changes:
|
||||||
|
|
||||||
|
* `ApproveTx` -> `ui_approveTx`
|
||||||
|
* `ApproveSignData` -> `ui_approveSignData`
|
||||||
|
* `ApproveExport` -> `removed`
|
||||||
|
* `ApproveImport` -> `removed`
|
||||||
|
* `ApproveListing` -> `ui_approveListing`
|
||||||
|
* `ApproveNewAccount` -> `ui_approveNewAccount`
|
||||||
|
* `ShowError` -> `ui_showError`
|
||||||
|
* `ShowInfo` -> `ui_showInfo`
|
||||||
|
* `OnApprovedTx` -> `ui_onApprovedTx`
|
||||||
|
* `OnSignerStartup` -> `ui_onSignerStartup`
|
||||||
|
* `OnInputRequired` -> `ui_onInputRequired`
|
||||||
|
|
||||||
|
|
||||||
### 4.0.0
|
### 4.0.0
|
||||||
|
|
||||||
* Bidirectional communication implemented, so the UI can query `clef` via the stdin/stdout RPC channel. Methods implemented are:
|
* Bidirectional communication implemented, so the UI can query `clef` via the stdin/stdout RPC channel. Methods implemented are:
|
||||||
|
228
cmd/clef/main.go
228
cmd/clef/main.go
@ -119,7 +119,7 @@ var (
|
|||||||
ruleFlag = cli.StringFlag{
|
ruleFlag = cli.StringFlag{
|
||||||
Name: "rules",
|
Name: "rules",
|
||||||
Usage: "Enable rule-engine",
|
Usage: "Enable rule-engine",
|
||||||
Value: "rules.json",
|
Value: "",
|
||||||
}
|
}
|
||||||
stdiouiFlag = cli.BoolFlag{
|
stdiouiFlag = cli.BoolFlag{
|
||||||
Name: "stdio-ui",
|
Name: "stdio-ui",
|
||||||
@ -371,17 +371,14 @@ func signer(c *cli.Context) error {
|
|||||||
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal)
|
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
api core.ExternalAPI
|
api core.ExternalAPI
|
||||||
|
pwStorage storage.Storage = &storage.NoStorage{}
|
||||||
)
|
)
|
||||||
|
|
||||||
configDir := c.GlobalString(configdirFlag.Name)
|
configDir := c.GlobalString(configdirFlag.Name)
|
||||||
if stretchedKey, err := readMasterKey(c, ui); err != nil {
|
if stretchedKey, err := readMasterKey(c, ui); err != nil {
|
||||||
log.Info("No master seed provided, rules disabled", "error", err)
|
log.Info("No master seed provided, rules disabled", "error", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf(err.Error())
|
|
||||||
}
|
|
||||||
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
||||||
|
|
||||||
// Generate domain specific keys
|
// Generate domain specific keys
|
||||||
@ -390,30 +387,31 @@ func signer(c *cli.Context) error {
|
|||||||
confkey := crypto.Keccak256([]byte("config"), stretchedKey)
|
confkey := crypto.Keccak256([]byte("config"), stretchedKey)
|
||||||
|
|
||||||
// Initialize the encrypted storages
|
// Initialize the encrypted storages
|
||||||
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
|
pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
|
||||||
jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
|
jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
|
||||||
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
|
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
|
||||||
|
|
||||||
//Do we have a rule-file?
|
//Do we have a rule-file?
|
||||||
ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFlag.Name))
|
if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" {
|
||||||
if err != nil {
|
ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFile))
|
||||||
log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
|
if err != nil {
|
||||||
} else {
|
log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
|
||||||
hasher := sha256.New()
|
|
||||||
hasher.Write(ruleJS)
|
|
||||||
shasum := hasher.Sum(nil)
|
|
||||||
storedShasum := configStorage.Get("ruleset_sha256")
|
|
||||||
if storedShasum != hex.EncodeToString(shasum) {
|
|
||||||
log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum)
|
|
||||||
} else {
|
} else {
|
||||||
// Initialize rules
|
shasum := sha256.Sum256(ruleJS)
|
||||||
ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage)
|
foundShaSum := hex.EncodeToString(shasum[:])
|
||||||
if err != nil {
|
storedShasum := configStorage.Get("ruleset_sha256")
|
||||||
utils.Fatalf(err.Error())
|
if storedShasum != foundShaSum {
|
||||||
|
log.Info("Could not validate ruleset hash, rules not enabled", "got", foundShaSum, "expected", storedShasum)
|
||||||
|
} else {
|
||||||
|
// Initialize rules
|
||||||
|
ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
ruleEngine.Init(string(ruleJS))
|
||||||
|
ui = ruleEngine
|
||||||
|
log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
|
||||||
}
|
}
|
||||||
ruleEngine.Init(string(ruleJS))
|
|
||||||
ui = ruleEngine
|
|
||||||
log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -427,7 +425,7 @@ func signer(c *cli.Context) error {
|
|||||||
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
|
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
|
||||||
"light-kdf", lightKdf, "advanced", advanced)
|
"light-kdf", lightKdf, "advanced", advanced)
|
||||||
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf)
|
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf)
|
||||||
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced)
|
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
|
||||||
|
|
||||||
// Establish the bidirectional communication, by creating a new UI backend and registering
|
// Establish the bidirectional communication, by creating a new UI backend and registering
|
||||||
// it with the UI.
|
// it with the UI.
|
||||||
@ -640,68 +638,124 @@ func testExternalUI(api *core.SignerAPI) {
|
|||||||
ctx := context.WithValue(context.Background(), "remote", "clef binary")
|
ctx := context.WithValue(context.Background(), "remote", "clef binary")
|
||||||
ctx = context.WithValue(ctx, "scheme", "in-proc")
|
ctx = context.WithValue(ctx, "scheme", "in-proc")
|
||||||
ctx = context.WithValue(ctx, "local", "main")
|
ctx = context.WithValue(ctx, "local", "main")
|
||||||
|
|
||||||
errs := make([]string, 0)
|
errs := make([]string, 0)
|
||||||
|
|
||||||
api.UI.ShowInfo("Testing 'ShowInfo'")
|
a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
|
||||||
api.UI.ShowError("Testing 'ShowError'")
|
|
||||||
|
|
||||||
checkErr := func(method string, err error) {
|
queryUser := func(q string) string {
|
||||||
if err != nil && err != core.ErrRequestDenied {
|
resp, err := api.UI.OnInputRequired(core.UserInputRequest{
|
||||||
errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error()))
|
Title: "Testing",
|
||||||
|
Prompt: q,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
}
|
||||||
|
return resp.Text
|
||||||
|
}
|
||||||
|
expectResponse := func(testcase, question, expect string) {
|
||||||
|
if got := queryUser(question); got != expect {
|
||||||
|
errs = append(errs, fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var err error
|
expectApprove := func(testcase string, err error) {
|
||||||
|
if err == nil || err == accounts.ErrUnknownAccount {
|
||||||
cliqueHeader := types.Header{
|
return
|
||||||
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.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)
|
|
||||||
checkErr("New", err)
|
|
||||||
|
|
||||||
api.UI.ShowInfo("Tests completed")
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
|
||||||
log.Error("Got errors")
|
|
||||||
for _, e := range errs {
|
|
||||||
log.Error(e)
|
|
||||||
}
|
}
|
||||||
} else {
|
errs = append(errs, fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
|
||||||
log.Info("No errors")
|
|
||||||
}
|
}
|
||||||
|
expectDeny := func(testcase string, err error) {
|
||||||
|
if err == nil || err != core.ErrRequestDenied {
|
||||||
|
errs = append(errs, fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test display of info and error
|
||||||
|
{
|
||||||
|
api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
|
||||||
|
expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
|
||||||
|
api.UI.ShowError("If you see this message, enter 'yes' to the next question")
|
||||||
|
expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
|
||||||
|
}
|
||||||
|
{ // Sign data test - clique header
|
||||||
|
api.UI.ShowInfo("Please approve the next request for signing a clique header")
|
||||||
|
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, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
|
||||||
|
_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
|
||||||
|
expectApprove("signdata - clique header", err)
|
||||||
|
}
|
||||||
|
{ // Sign data test - plain text
|
||||||
|
api.UI.ShowInfo("Please approve the next request for signing text")
|
||||||
|
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
|
||||||
|
_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
|
||||||
|
expectApprove("signdata - text", err)
|
||||||
|
}
|
||||||
|
{ // Sign data test - plain text reject
|
||||||
|
api.UI.ShowInfo("Please deny the next request for signing text")
|
||||||
|
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
|
||||||
|
_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
|
||||||
|
expectDeny("signdata - text", err)
|
||||||
|
}
|
||||||
|
{ // Sign transaction
|
||||||
|
|
||||||
|
api.UI.ShowInfo("Please reject next transaction")
|
||||||
|
data := hexutil.Bytes([]byte{})
|
||||||
|
to := common.NewMixedcaseAddress(a)
|
||||||
|
tx := core.SendTxArgs{
|
||||||
|
Data: &data,
|
||||||
|
Nonce: 0x1,
|
||||||
|
Value: hexutil.Big(*big.NewInt(6)),
|
||||||
|
From: common.NewMixedcaseAddress(a),
|
||||||
|
To: &to,
|
||||||
|
GasPrice: hexutil.Big(*big.NewInt(5)),
|
||||||
|
Gas: 1000,
|
||||||
|
Input: nil,
|
||||||
|
}
|
||||||
|
_, err := api.SignTransaction(ctx, tx, nil)
|
||||||
|
expectDeny("signtransaction [1]", err)
|
||||||
|
expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no")
|
||||||
|
}
|
||||||
|
{ // Listing
|
||||||
|
api.UI.ShowInfo("Please reject listing-request")
|
||||||
|
_, err := api.List(ctx)
|
||||||
|
expectDeny("list", err)
|
||||||
|
}
|
||||||
|
{ // Import
|
||||||
|
api.UI.ShowInfo("Please reject new account-request")
|
||||||
|
_, err := api.New(ctx)
|
||||||
|
expectDeny("newaccount", err)
|
||||||
|
}
|
||||||
|
{ // Metadata
|
||||||
|
api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
|
||||||
|
api.List(context.WithValue(ctx, "Origin", "origin.com"))
|
||||||
|
expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range errs {
|
||||||
|
log.Error(e)
|
||||||
|
}
|
||||||
|
result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n"))
|
||||||
|
api.UI.ShowInfo(result)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPassPhrase retrieves the password associated with clef, either fetched
|
// getPassPhrase retrieves the password associated with clef, either fetched
|
||||||
@ -798,7 +852,7 @@ func GenDoc(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
{ // Sign plain text response
|
{ // Sign plain text response
|
||||||
add("SignDataResponse - approve", "Response to SignDataRequest",
|
add("SignDataResponse - approve", "Response to SignDataRequest",
|
||||||
&core.SignDataResponse{Password: "apassword", Approved: true})
|
&core.SignDataResponse{Approved: true})
|
||||||
add("SignDataResponse - deny", "Response to SignDataRequest",
|
add("SignDataResponse - deny", "Response to SignDataRequest",
|
||||||
&core.SignDataResponse{})
|
&core.SignDataResponse{})
|
||||||
}
|
}
|
||||||
@ -833,9 +887,9 @@ func GenDoc(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
{ // Sign tx response
|
{ // Sign tx response
|
||||||
data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
|
data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
|
||||||
add("SignDataResponse - approve", "Response to SignDataRequest. This response needs to contain the `transaction`"+
|
add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+
|
||||||
", because the UI is free to make modifications to the transaction.",
|
", because the UI is free to make modifications to the transaction.",
|
||||||
&core.SignTxResponse{Password: "apassword", Approved: true,
|
&core.SignTxResponse{Approved: true,
|
||||||
Transaction: core.SendTxArgs{
|
Transaction: core.SendTxArgs{
|
||||||
Data: &data,
|
Data: &data,
|
||||||
Nonce: 0x4,
|
Nonce: 0x4,
|
||||||
@ -846,9 +900,9 @@ func GenDoc(ctx *cli.Context) {
|
|||||||
Gas: 1000,
|
Gas: 1000,
|
||||||
Input: nil,
|
Input: nil,
|
||||||
}})
|
}})
|
||||||
add("SignDataResponse - deny", "Response to SignDataRequest. When denying a request, there's no need to "+
|
add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+
|
||||||
"provide the transaction in return",
|
"provide the transaction in return",
|
||||||
&core.SignDataResponse{})
|
&core.SignTxResponse{})
|
||||||
}
|
}
|
||||||
{ // WHen a signed tx is ready to go out
|
{ // WHen a signed tx is ready to go out
|
||||||
desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
|
desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
|
||||||
@ -874,7 +928,7 @@ func GenDoc(ctx *cli.Context) {
|
|||||||
{ // User input
|
{ // User input
|
||||||
add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
|
add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
|
||||||
&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
|
&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
|
||||||
add("UserInputResponse", "Response to SignDataRequest",
|
add("UserInputResponse", "Response to UserInputRequest",
|
||||||
&core.UserInputResponse{Text: "The textual response from user"})
|
&core.UserInputResponse{Text: "The textual response from user"})
|
||||||
}
|
}
|
||||||
{ // List request
|
{ // List request
|
||||||
@ -888,7 +942,7 @@ func GenDoc(ctx *cli.Context) {
|
|||||||
{b, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
|
{b, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
add("UserInputResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
|
add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
|
||||||
"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
|
"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
|
||||||
&core.ListResponse{
|
&core.ListResponse{
|
||||||
Accounts: []accounts.Account{
|
Accounts: []accounts.Account{
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
@ -32,6 +33,7 @@ import (
|
|||||||
"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"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -40,7 +42,7 @@ const (
|
|||||||
// ExternalAPIVersion -- see extapi_changelog.md
|
// ExternalAPIVersion -- see extapi_changelog.md
|
||||||
ExternalAPIVersion = "6.0.0"
|
ExternalAPIVersion = "6.0.0"
|
||||||
// InternalAPIVersion -- see intapi_changelog.md
|
// InternalAPIVersion -- see intapi_changelog.md
|
||||||
InternalAPIVersion = "4.0.0"
|
InternalAPIVersion = "6s.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExternalAPI defines the external API through which signing requests are made.
|
// ExternalAPI defines the external API through which signing requests are made.
|
||||||
@ -68,10 +70,6 @@ type UIClientAPI interface {
|
|||||||
ApproveTx(request *SignTxRequest) (SignTxResponse, error)
|
ApproveTx(request *SignTxRequest) (SignTxResponse, error)
|
||||||
// ApproveSignData prompt the user for confirmation to request to sign data
|
// ApproveSignData prompt the user for confirmation to request to sign data
|
||||||
ApproveSignData(request *SignDataRequest) (SignDataResponse, error)
|
ApproveSignData(request *SignDataRequest) (SignDataResponse, error)
|
||||||
// ApproveExport prompt the user for confirmation to export encrypted Account json
|
|
||||||
ApproveExport(request *ExportRequest) (ExportResponse, error)
|
|
||||||
// ApproveImport prompt the user for confirmation to import Account json
|
|
||||||
ApproveImport(request *ImportRequest) (ImportResponse, error)
|
|
||||||
// ApproveListing prompt the user for confirmation to list accounts
|
// ApproveListing prompt the user for confirmation to list accounts
|
||||||
// the list of accounts to list can be modified by the UI
|
// the list of accounts to list can be modified by the UI
|
||||||
ApproveListing(request *ListRequest) (ListResponse, error)
|
ApproveListing(request *ListRequest) (ListResponse, error)
|
||||||
@ -96,11 +94,12 @@ type UIClientAPI interface {
|
|||||||
|
|
||||||
// SignerAPI defines the actual implementation of ExternalAPI
|
// SignerAPI defines the actual implementation of ExternalAPI
|
||||||
type SignerAPI struct {
|
type SignerAPI struct {
|
||||||
chainID *big.Int
|
chainID *big.Int
|
||||||
am *accounts.Manager
|
am *accounts.Manager
|
||||||
UI UIClientAPI
|
UI UIClientAPI
|
||||||
validator *Validator
|
validator *Validator
|
||||||
rejectMode bool
|
rejectMode bool
|
||||||
|
credentials storage.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata about a request
|
// Metadata about a request
|
||||||
@ -187,25 +186,6 @@ type (
|
|||||||
//The UI may make changes to the TX
|
//The UI may make changes to the TX
|
||||||
Transaction SendTxArgs `json:"transaction"`
|
Transaction SendTxArgs `json:"transaction"`
|
||||||
Approved bool `json:"approved"`
|
Approved bool `json:"approved"`
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
// ExportRequest info about query to export accounts
|
|
||||||
ExportRequest struct {
|
|
||||||
Address common.Address `json:"address"`
|
|
||||||
Meta Metadata `json:"meta"`
|
|
||||||
}
|
|
||||||
// ExportResponse response to export-request
|
|
||||||
ExportResponse struct {
|
|
||||||
Approved bool `json:"approved"`
|
|
||||||
}
|
|
||||||
// ImportRequest info about request to import an Account
|
|
||||||
ImportRequest struct {
|
|
||||||
Meta Metadata `json:"meta"`
|
|
||||||
}
|
|
||||||
ImportResponse struct {
|
|
||||||
Approved bool `json:"approved"`
|
|
||||||
OldPassword string `json:"old_password"`
|
|
||||||
NewPassword string `json:"new_password"`
|
|
||||||
}
|
}
|
||||||
SignDataRequest struct {
|
SignDataRequest struct {
|
||||||
ContentType string `json:"content_type"`
|
ContentType string `json:"content_type"`
|
||||||
@ -217,14 +197,12 @@ type (
|
|||||||
}
|
}
|
||||||
SignDataResponse struct {
|
SignDataResponse struct {
|
||||||
Approved bool `json:"approved"`
|
Approved bool `json:"approved"`
|
||||||
Password string
|
|
||||||
}
|
}
|
||||||
NewAccountRequest struct {
|
NewAccountRequest struct {
|
||||||
Meta Metadata `json:"meta"`
|
Meta Metadata `json:"meta"`
|
||||||
}
|
}
|
||||||
NewAccountResponse struct {
|
NewAccountResponse struct {
|
||||||
Approved bool `json:"approved"`
|
Approved bool `json:"approved"`
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
}
|
||||||
ListRequest struct {
|
ListRequest struct {
|
||||||
Accounts []accounts.Account `json:"accounts"`
|
Accounts []accounts.Account `json:"accounts"`
|
||||||
@ -240,8 +218,8 @@ type (
|
|||||||
Info map[string]interface{} `json:"info"`
|
Info map[string]interface{} `json:"info"`
|
||||||
}
|
}
|
||||||
UserInputRequest struct {
|
UserInputRequest struct {
|
||||||
Prompt string `json:"prompt"`
|
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
IsPassword bool `json:"isPassword"`
|
IsPassword bool `json:"isPassword"`
|
||||||
}
|
}
|
||||||
UserInputResponse struct {
|
UserInputResponse struct {
|
||||||
@ -256,11 +234,11 @@ var ErrRequestDenied = errors.New("Request denied")
|
|||||||
// key that is generated when a new Account is created.
|
// key that is generated when a new Account is created.
|
||||||
// noUSB disables USB support that is required to support hardware devices such as
|
// noUSB disables USB support that is required to support hardware devices such as
|
||||||
// ledger and trezor.
|
// ledger and trezor.
|
||||||
func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, abidb *AbiDb, advancedMode bool) *SignerAPI {
|
func NewSignerAPI(am *accounts.Manager, chainID int64, noUSB bool, ui UIClientAPI, abidb *AbiDb, advancedMode bool, credentials storage.Storage) *SignerAPI {
|
||||||
if advancedMode {
|
if advancedMode {
|
||||||
log.Info("Clef is in advanced mode: will warn instead of reject")
|
log.Info("Clef is in advanced mode: will warn instead of reject")
|
||||||
}
|
}
|
||||||
signer := &SignerAPI{big.NewInt(chainID), am, ui, NewValidator(abidb), !advancedMode}
|
signer := &SignerAPI{big.NewInt(chainID), am, ui, NewValidator(abidb), !advancedMode, credentials}
|
||||||
if !noUSB {
|
if !noUSB {
|
||||||
signer.startUSBListener()
|
signer.startUSBListener()
|
||||||
}
|
}
|
||||||
@ -381,24 +359,27 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) {
|
|||||||
if len(be) == 0 {
|
if len(be) == 0 {
|
||||||
return common.Address{}, errors.New("password based accounts not supported")
|
return common.Address{}, errors.New("password based accounts not supported")
|
||||||
}
|
}
|
||||||
var (
|
if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil {
|
||||||
resp NewAccountResponse
|
return common.Address{}, err
|
||||||
err error
|
} else if !resp.Approved {
|
||||||
)
|
return common.Address{}, ErrRequestDenied
|
||||||
|
}
|
||||||
|
|
||||||
// Three retries to get a valid password
|
// Three retries to get a valid password
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
resp, err = api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)})
|
resp, err := api.UI.OnInputRequired(UserInputRequest{
|
||||||
|
"New account password",
|
||||||
|
fmt.Sprintf("Please enter a password for the new account to be created (attempt %d of 3)", i),
|
||||||
|
true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
log.Warn("error obtaining password", "attempt", i, "error", err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if !resp.Approved {
|
if pwErr := ValidatePasswordFormat(resp.Text); pwErr != nil {
|
||||||
return common.Address{}, ErrRequestDenied
|
|
||||||
}
|
|
||||||
if pwErr := ValidatePasswordFormat(resp.Password); pwErr != nil {
|
|
||||||
api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr))
|
api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr))
|
||||||
} else {
|
} else {
|
||||||
// No error
|
// No error
|
||||||
acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Password)
|
acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text)
|
||||||
return acc.Address, err
|
return acc.Address, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,6 +433,24 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
|
|||||||
return modified
|
return modified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *SignerAPI) lookupPassword(address common.Address) string {
|
||||||
|
return api.credentials.Get(strings.ToLower(address.String()))
|
||||||
|
}
|
||||||
|
func (api *SignerAPI) lookupOrQueryPassword(address common.Address, title, prompt string) (string, error) {
|
||||||
|
if pw := api.lookupPassword(address); pw != "" {
|
||||||
|
return pw, nil
|
||||||
|
} else {
|
||||||
|
pwResp, err := api.UI.OnInputRequired(UserInputRequest{title, prompt, true})
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("error obtaining password", "error", err)
|
||||||
|
// We'll not forward the error here, in case the error contains info about the response from the UI,
|
||||||
|
// which could leak the password if it was malformed json or something
|
||||||
|
return "", errors.New("internal error")
|
||||||
|
}
|
||||||
|
return pwResp.Text, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form
|
// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form
|
||||||
func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
|
func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
|
||||||
var (
|
var (
|
||||||
@ -495,9 +494,14 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth
|
|||||||
}
|
}
|
||||||
// Convert fields into a real transaction
|
// Convert fields into a real transaction
|
||||||
var unsignedTx = result.Transaction.toTransaction()
|
var unsignedTx = result.Transaction.toTransaction()
|
||||||
|
// Get the password for the transaction
|
||||||
|
pw, err := api.lookupOrQueryPassword(acc.Address, "Account password",
|
||||||
|
fmt.Sprintf("Please enter the password for account %s", acc.Address.String()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// The one to sign is the one that was returned from the UI
|
// The one to sign is the one that was returned from the UI
|
||||||
signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, unsignedTx, api.chainID)
|
signedTx, err := wallet.SignTxWithPassphrase(acc, pw, unsignedTx, api.chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.UI.ShowError(err.Error())
|
api.UI.ShowError(err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -19,7 +19,6 @@ package core
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -35,62 +34,49 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Used for testing
|
//Used for testing
|
||||||
type HeadlessUI struct {
|
type headlessUi struct {
|
||||||
controller chan string
|
approveCh chan string // to send approve/deny
|
||||||
|
inputCh chan string // to send password
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
|
func (ui *headlessUi) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
|
||||||
return UserInputResponse{}, errors.New("not implemented")
|
input := <-ui.inputCh
|
||||||
|
return UserInputResponse{Text: input}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) {
|
func (ui *headlessUi) OnSignerStartup(info StartupInfo) {}
|
||||||
}
|
func (ui *headlessUi) RegisterUIServer(api *UIServerAPI) {}
|
||||||
func (ui *HeadlessUI) RegisterUIServer(api *UIServerAPI) {
|
func (ui *headlessUi) OnApprovedTx(tx ethapi.SignTransactionResult) {}
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
func (ui *headlessUi) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
||||||
fmt.Printf("OnApproved()\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
switch <-ui.approveCh {
|
||||||
|
|
||||||
switch <-ui.controller {
|
|
||||||
case "Y":
|
case "Y":
|
||||||
return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
|
return SignTxResponse{request.Transaction, true}, nil
|
||||||
case "M": //Modify
|
case "M": // modify
|
||||||
|
// The headless UI always modifies the transaction
|
||||||
old := big.Int(request.Transaction.Value)
|
old := big.Int(request.Transaction.Value)
|
||||||
newVal := big.NewInt(0).Add(&old, big.NewInt(1))
|
newVal := big.NewInt(0).Add(&old, big.NewInt(1))
|
||||||
request.Transaction.Value = hexutil.Big(*newVal)
|
request.Transaction.Value = hexutil.Big(*newVal)
|
||||||
return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
|
return SignTxResponse{request.Transaction, true}, nil
|
||||||
default:
|
default:
|
||||||
return SignTxResponse{request.Transaction, false, ""}, nil
|
return SignTxResponse{request.Transaction, false}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
func (ui *headlessUi) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
||||||
if "Y" == <-ui.controller {
|
approved := "Y" == <-ui.approveCh
|
||||||
return SignDataResponse{true, <-ui.controller}, nil
|
return SignDataResponse{approved}, nil
|
||||||
}
|
|
||||||
return SignDataResponse{false, ""}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
func (ui *headlessUi) ApproveListing(request *ListRequest) (ListResponse, error) {
|
||||||
return ExportResponse{<-ui.controller == "Y"}, nil
|
approval := <-ui.approveCh
|
||||||
|
//fmt.Printf("approval %s\n", approval)
|
||||||
}
|
switch approval {
|
||||||
|
|
||||||
func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
|
||||||
if "Y" == <-ui.controller {
|
|
||||||
return ImportResponse{true, <-ui.controller, <-ui.controller}, nil
|
|
||||||
}
|
|
||||||
return ImportResponse{false, "", ""}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) {
|
|
||||||
switch <-ui.controller {
|
|
||||||
case "A":
|
case "A":
|
||||||
return ListResponse{request.Accounts}, nil
|
return ListResponse{request.Accounts}, nil
|
||||||
case "1":
|
case "1":
|
||||||
@ -102,19 +88,19 @@ func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
func (ui *headlessUi) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
||||||
if "Y" == <-ui.controller {
|
if "Y" == <-ui.approveCh {
|
||||||
return NewAccountResponse{true, <-ui.controller}, nil
|
return NewAccountResponse{true}, nil
|
||||||
}
|
}
|
||||||
return NewAccountResponse{false, ""}, nil
|
return NewAccountResponse{false}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) ShowError(message string) {
|
func (ui *headlessUi) ShowError(message string) {
|
||||||
//stdout is used by communication
|
//stdout is used by communication
|
||||||
fmt.Fprintln(os.Stderr, message)
|
fmt.Fprintln(os.Stderr, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) ShowInfo(message string) {
|
func (ui *headlessUi) ShowInfo(message string) {
|
||||||
//stdout is used by communication
|
//stdout is used by communication
|
||||||
fmt.Fprintln(os.Stderr, message)
|
fmt.Fprintln(os.Stderr, message)
|
||||||
}
|
}
|
||||||
@ -131,25 +117,20 @@ func tmpDirName(t *testing.T) string {
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(t *testing.T) (*SignerAPI, chan string) {
|
func setup(t *testing.T) (*SignerAPI, *headlessUi) {
|
||||||
|
|
||||||
controller := make(chan string, 20)
|
|
||||||
|
|
||||||
db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
|
db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err.Error())
|
t.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
var (
|
ui := &headlessUi{make(chan string, 20), make(chan string, 20)}
|
||||||
ui = &HeadlessUI{controller}
|
am := StartClefAccountManager(tmpDirName(t), true, true)
|
||||||
am = StartClefAccountManager(tmpDirName(t), true, true)
|
api := NewSignerAPI(am, 1337, true, ui, db, true, &storage.NoStorage{})
|
||||||
api = NewSignerAPI(am, 1337, true, ui, db, true)
|
return api, ui
|
||||||
)
|
|
||||||
return api, controller
|
|
||||||
}
|
|
||||||
func createAccount(control chan string, api *SignerAPI, t *testing.T) {
|
|
||||||
|
|
||||||
control <- "Y"
|
}
|
||||||
control <- "a_long_password"
|
func createAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
|
||||||
|
ui.approveCh <- "Y"
|
||||||
|
ui.inputCh <- "a_long_password"
|
||||||
_, err := api.New(context.Background())
|
_, err := api.New(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -158,14 +139,13 @@ func createAccount(control chan string, api *SignerAPI, t *testing.T) {
|
|||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func failCreateAccountWithPassword(control chan string, api *SignerAPI, password string, t *testing.T) {
|
func failCreateAccountWithPassword(ui *headlessUi, api *SignerAPI, password string, t *testing.T) {
|
||||||
|
|
||||||
control <- "Y"
|
ui.approveCh <- "Y"
|
||||||
control <- password
|
// We will be asked three times to provide a suitable password
|
||||||
control <- "Y"
|
ui.inputCh <- password
|
||||||
control <- password
|
ui.inputCh <- password
|
||||||
control <- "Y"
|
ui.inputCh <- password
|
||||||
control <- password
|
|
||||||
|
|
||||||
addr, err := api.New(context.Background())
|
addr, err := api.New(context.Background())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -176,8 +156,8 @@ func failCreateAccountWithPassword(control chan string, api *SignerAPI, password
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) {
|
func failCreateAccount(ui *headlessUi, api *SignerAPI, t *testing.T) {
|
||||||
control <- "N"
|
ui.approveCh <- "N"
|
||||||
addr, err := api.New(context.Background())
|
addr, err := api.New(context.Background())
|
||||||
if err != ErrRequestDenied {
|
if err != ErrRequestDenied {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -187,19 +167,20 @@ func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func list(control chan string, api *SignerAPI, t *testing.T) []common.Address {
|
func list(ui *headlessUi, api *SignerAPI, t *testing.T) ([]common.Address, error) {
|
||||||
control <- "A"
|
ui.approveCh <- "A"
|
||||||
list, err := api.List(context.Background())
|
return api.List(context.Background())
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewAcc(t *testing.T) {
|
func TestNewAcc(t *testing.T) {
|
||||||
api, control := setup(t)
|
api, control := setup(t)
|
||||||
verifyNum := func(num int) {
|
verifyNum := func(num int) {
|
||||||
if list := list(control, api, t); len(list) != num {
|
list, err := list(control, api, t)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if len(list) != num {
|
||||||
t.Errorf("Expected %d accounts, got %d", num, len(list))
|
t.Errorf("Expected %d accounts, got %d", num, len(list))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,18 +193,16 @@ func TestNewAcc(t *testing.T) {
|
|||||||
failCreateAccount(control, api, t)
|
failCreateAccount(control, api, t)
|
||||||
createAccount(control, api, t)
|
createAccount(control, api, t)
|
||||||
failCreateAccount(control, api, t)
|
failCreateAccount(control, api, t)
|
||||||
|
|
||||||
verifyNum(4)
|
verifyNum(4)
|
||||||
|
|
||||||
// Fail to create this, due to bad password
|
// Fail to create this, due to bad password
|
||||||
failCreateAccountWithPassword(control, api, "short", t)
|
failCreateAccountWithPassword(control, api, "short", t)
|
||||||
failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
|
failCreateAccountWithPassword(control, api, "longerbutbad\rfoo", t)
|
||||||
|
|
||||||
verifyNum(4)
|
verifyNum(4)
|
||||||
|
|
||||||
// Testing listing:
|
// Testing listing:
|
||||||
// Listing one Account
|
// Listing one Account
|
||||||
control <- "1"
|
control.approveCh <- "1"
|
||||||
list, err := api.List(context.Background())
|
list, err := api.List(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -232,7 +211,7 @@ func TestNewAcc(t *testing.T) {
|
|||||||
t.Fatalf("List should only show one Account")
|
t.Fatalf("List should only show one Account")
|
||||||
}
|
}
|
||||||
// Listing denied
|
// Listing denied
|
||||||
control <- "Nope"
|
control.approveCh <- "Nope"
|
||||||
list, err = api.List(context.Background())
|
list, err = api.List(context.Background())
|
||||||
if len(list) != 0 {
|
if len(list) != 0 {
|
||||||
t.Fatalf("List should be empty")
|
t.Fatalf("List should be empty")
|
||||||
@ -269,7 +248,7 @@ func TestSignTx(t *testing.T) {
|
|||||||
|
|
||||||
api, control := setup(t)
|
api, control := setup(t)
|
||||||
createAccount(control, api, t)
|
createAccount(control, api, t)
|
||||||
control <- "A"
|
control.approveCh <- "A"
|
||||||
list, err = api.List(context.Background())
|
list, err = api.List(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -279,8 +258,8 @@ func TestSignTx(t *testing.T) {
|
|||||||
methodSig := "test(uint)"
|
methodSig := "test(uint)"
|
||||||
tx := mkTestTx(a)
|
tx := mkTestTx(a)
|
||||||
|
|
||||||
control <- "Y"
|
control.approveCh <- "Y"
|
||||||
control <- "wrongpassword"
|
control.inputCh <- "wrongpassword"
|
||||||
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||||
if res != nil {
|
if res != nil {
|
||||||
t.Errorf("Expected nil-response, got %v", res)
|
t.Errorf("Expected nil-response, got %v", res)
|
||||||
@ -288,7 +267,7 @@ func TestSignTx(t *testing.T) {
|
|||||||
if err != keystore.ErrDecrypt {
|
if err != keystore.ErrDecrypt {
|
||||||
t.Errorf("Expected ErrLocked! %v", err)
|
t.Errorf("Expected ErrLocked! %v", err)
|
||||||
}
|
}
|
||||||
control <- "No way"
|
control.approveCh <- "No way"
|
||||||
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||||
if res != nil {
|
if res != nil {
|
||||||
t.Errorf("Expected nil-response, got %v", res)
|
t.Errorf("Expected nil-response, got %v", res)
|
||||||
@ -296,8 +275,9 @@ func TestSignTx(t *testing.T) {
|
|||||||
if err != ErrRequestDenied {
|
if err != ErrRequestDenied {
|
||||||
t.Errorf("Expected ErrRequestDenied! %v", err)
|
t.Errorf("Expected ErrRequestDenied! %v", err)
|
||||||
}
|
}
|
||||||
control <- "Y"
|
// Sign with correct password
|
||||||
control <- "a_long_password"
|
control.approveCh <- "Y"
|
||||||
|
control.inputCh <- "a_long_password"
|
||||||
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
res, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -310,8 +290,8 @@ func TestSignTx(t *testing.T) {
|
|||||||
if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
|
if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
|
||||||
t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
|
t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
|
||||||
}
|
}
|
||||||
control <- "Y"
|
control.approveCh <- "Y"
|
||||||
control <- "a_long_password"
|
control.inputCh <- "a_long_password"
|
||||||
|
|
||||||
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -322,8 +302,8 @@ func TestSignTx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//The tx is modified by the UI
|
//The tx is modified by the UI
|
||||||
control <- "M"
|
control.approveCh <- "M"
|
||||||
control <- "a_long_password"
|
control.inputCh <- "a_long_password"
|
||||||
|
|
||||||
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -341,31 +321,3 @@ func TestSignTx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func TestAsyncronousResponses(t *testing.T){
|
|
||||||
|
|
||||||
//Set up one account
|
|
||||||
api, control := setup(t)
|
|
||||||
createAccount(control, api, t)
|
|
||||||
|
|
||||||
// Two transactions, the second one with larger value than the first
|
|
||||||
tx1 := mkTestTx()
|
|
||||||
newVal := big.NewInt(0).Add((*big.Int) (tx1.Value), big.NewInt(1))
|
|
||||||
tx2 := mkTestTx()
|
|
||||||
tx2.Value = (*hexutil.Big)(newVal)
|
|
||||||
|
|
||||||
control <- "W" //wait
|
|
||||||
control <- "Y" //
|
|
||||||
control <- "a_long_password"
|
|
||||||
control <- "Y" //
|
|
||||||
control <- "a_long_password"
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
h1, err := api.SignTransaction(context.Background(), common.HexToAddress("1111"), tx1, nil)
|
|
||||||
h2, err := api.SignTransaction(context.Background(), common.HexToAddress("2222"), tx2, nil)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
@ -87,9 +87,10 @@ func (ui *CommandlineUI) readPasswordText(inputstring string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ui *CommandlineUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
|
func (ui *CommandlineUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
|
||||||
fmt.Println(info.Title)
|
|
||||||
fmt.Println(info.Prompt)
|
fmt.Printf("## %s\n\n%s\n", info.Title, info.Prompt)
|
||||||
if info.IsPassword {
|
if info.IsPassword {
|
||||||
|
fmt.Printf("> ")
|
||||||
text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to read password", "err", err)
|
log.Error("Failed to read password", "err", err)
|
||||||
@ -156,9 +157,9 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
|
|||||||
showMetadata(request.Meta)
|
showMetadata(request.Meta)
|
||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
if !ui.confirm() {
|
if !ui.confirm() {
|
||||||
return SignTxResponse{request.Transaction, false, ""}, nil
|
return SignTxResponse{request.Transaction, false}, nil
|
||||||
}
|
}
|
||||||
return SignTxResponse{request.Transaction, true, ui.readPassword()}, nil
|
return SignTxResponse{request.Transaction, true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveSignData prompt the user for confirmation to request to sign data
|
// ApproveSignData prompt the user for confirmation to request to sign data
|
||||||
@ -178,40 +179,9 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
|
|||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
showMetadata(request.Meta)
|
showMetadata(request.Meta)
|
||||||
if !ui.confirm() {
|
if !ui.confirm() {
|
||||||
return SignDataResponse{false, ""}, nil
|
return SignDataResponse{false}, nil
|
||||||
}
|
}
|
||||||
return SignDataResponse{true, ui.readPassword()}, nil
|
return SignDataResponse{true}, nil
|
||||||
}
|
|
||||||
|
|
||||||
// ApproveExport prompt the user for confirmation to export encrypted Account json
|
|
||||||
func (ui *CommandlineUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
|
||||||
ui.mu.Lock()
|
|
||||||
defer ui.mu.Unlock()
|
|
||||||
|
|
||||||
fmt.Printf("-------- Export Account request--------------\n")
|
|
||||||
fmt.Printf("A request has been made to export the (encrypted) keyfile\n")
|
|
||||||
fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n")
|
|
||||||
fmt.Printf("\n")
|
|
||||||
fmt.Printf("Account: %x\n", request.Address)
|
|
||||||
//fmt.Printf("keyfile: \n%v\n", request.file)
|
|
||||||
fmt.Printf("-------------------------------------------\n")
|
|
||||||
showMetadata(request.Meta)
|
|
||||||
return ExportResponse{ui.confirm()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApproveImport prompt the user for confirmation to import Account json
|
|
||||||
func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
|
||||||
ui.mu.Lock()
|
|
||||||
defer ui.mu.Unlock()
|
|
||||||
|
|
||||||
fmt.Printf("-------- Import Account request--------------\n")
|
|
||||||
fmt.Printf("A request has been made to import an encrypted keyfile\n")
|
|
||||||
fmt.Printf("-------------------------------------------\n")
|
|
||||||
showMetadata(request.Meta)
|
|
||||||
if !ui.confirm() {
|
|
||||||
return ImportResponse{false, "", ""}, nil
|
|
||||||
}
|
|
||||||
return ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveListing prompt the user for confirmation to list accounts
|
// ApproveListing prompt the user for confirmation to list accounts
|
||||||
@ -248,21 +218,20 @@ func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccou
|
|||||||
fmt.Printf("and the address is returned to the external caller\n\n")
|
fmt.Printf("and the address is returned to the external caller\n\n")
|
||||||
showMetadata(request.Meta)
|
showMetadata(request.Meta)
|
||||||
if !ui.confirm() {
|
if !ui.confirm() {
|
||||||
return NewAccountResponse{false, ""}, nil
|
return NewAccountResponse{false}, nil
|
||||||
}
|
}
|
||||||
return NewAccountResponse{true, ui.readPassword()}, nil
|
return NewAccountResponse{true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowError displays error message to user
|
// ShowError displays error message to user
|
||||||
func (ui *CommandlineUI) ShowError(message string) {
|
func (ui *CommandlineUI) ShowError(message string) {
|
||||||
fmt.Printf("-------- Error message from Clef-----------\n")
|
fmt.Printf("## Error \n%s\n", message)
|
||||||
fmt.Println(message)
|
|
||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowInfo displays info message to user
|
// ShowInfo displays info message to user
|
||||||
func (ui *CommandlineUI) ShowInfo(message string) {
|
func (ui *CommandlineUI) ShowInfo(message string) {
|
||||||
fmt.Printf("Info: %v\n", message)
|
fmt.Printf("## Info \n%s\n", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
||||||
|
@ -139,8 +139,14 @@ func (api *SignerAPI) sign(addr common.MixedcaseAddress, req *SignDataRequest, l
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Sign the data with the wallet
|
||||||
signature, err := wallet.SignDataWithPassphrase(account, res.Password, req.ContentType, req.Rawdata)
|
signature, err := wallet.SignDataWithPassphrase(account, pw, req.ContentType, req.Rawdata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -179,15 +179,15 @@ func TestSignData(t *testing.T) {
|
|||||||
//Create two accounts
|
//Create two accounts
|
||||||
createAccount(control, api, t)
|
createAccount(control, api, t)
|
||||||
createAccount(control, api, t)
|
createAccount(control, api, t)
|
||||||
control <- "1"
|
control.approveCh <- "1"
|
||||||
list, err := api.List(context.Background())
|
list, err := api.List(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
a := common.NewMixedcaseAddress(list[0])
|
a := common.NewMixedcaseAddress(list[0])
|
||||||
|
|
||||||
control <- "Y"
|
control.approveCh <- "Y"
|
||||||
control <- "wrongpassword"
|
control.inputCh <- "wrongpassword"
|
||||||
signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
signature, err := api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||||
if signature != nil {
|
if signature != nil {
|
||||||
t.Errorf("Expected nil-data, got %x", signature)
|
t.Errorf("Expected nil-data, got %x", signature)
|
||||||
@ -195,7 +195,7 @@ func TestSignData(t *testing.T) {
|
|||||||
if err != keystore.ErrDecrypt {
|
if err != keystore.ErrDecrypt {
|
||||||
t.Errorf("Expected ErrLocked! '%v'", err)
|
t.Errorf("Expected ErrLocked! '%v'", err)
|
||||||
}
|
}
|
||||||
control <- "No way"
|
control.approveCh <- "No way"
|
||||||
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||||
if signature != nil {
|
if signature != nil {
|
||||||
t.Errorf("Expected nil-data, got %x", signature)
|
t.Errorf("Expected nil-data, got %x", signature)
|
||||||
@ -204,8 +204,8 @@ func TestSignData(t *testing.T) {
|
|||||||
t.Errorf("Expected ErrRequestDenied! '%v'", err)
|
t.Errorf("Expected ErrRequestDenied! '%v'", err)
|
||||||
}
|
}
|
||||||
// text/plain
|
// text/plain
|
||||||
control <- "Y"
|
control.approveCh <- "Y"
|
||||||
control <- "a_long_password"
|
control.inputCh <- "a_long_password"
|
||||||
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
signature, err = api.SignData(context.Background(), TextPlain.Mime, a, hexutil.Encode([]byte("EHLO world")))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -214,8 +214,8 @@ func TestSignData(t *testing.T) {
|
|||||||
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
|
t.Errorf("Expected 65 byte signature (got %d bytes)", len(signature))
|
||||||
}
|
}
|
||||||
// data/typed
|
// data/typed
|
||||||
control <- "Y"
|
control.approveCh <- "Y"
|
||||||
control <- "a_long_password"
|
control.inputCh <- "a_long_password"
|
||||||
signature, err = api.SignTypedData(context.Background(), a, typedData)
|
signature, err = api.SignTypedData(context.Background(), a, typedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -65,71 +65,59 @@ func (ui *StdIOUI) notify(serviceMethod string, args interface{}) error {
|
|||||||
|
|
||||||
func (ui *StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
func (ui *StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
||||||
var result SignTxResponse
|
var result SignTxResponse
|
||||||
err := ui.dispatch("ApproveTx", request, &result)
|
err := ui.dispatch("ui_approveTx", request, &result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *StdIOUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
func (ui *StdIOUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
||||||
var result SignDataResponse
|
var result SignDataResponse
|
||||||
err := ui.dispatch("ApproveSignData", request, &result)
|
err := ui.dispatch("ui_approveSignData", request, &result)
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *StdIOUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
|
||||||
var result ExportResponse
|
|
||||||
err := ui.dispatch("ApproveExport", request, &result)
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *StdIOUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
|
||||||
var result ImportResponse
|
|
||||||
err := ui.dispatch("ApproveImport", request, &result)
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *StdIOUI) ApproveListing(request *ListRequest) (ListResponse, error) {
|
func (ui *StdIOUI) ApproveListing(request *ListRequest) (ListResponse, error) {
|
||||||
var result ListResponse
|
var result ListResponse
|
||||||
err := ui.dispatch("ApproveListing", request, &result)
|
err := ui.dispatch("ui_approveListing", request, &result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
func (ui *StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
||||||
var result NewAccountResponse
|
var result NewAccountResponse
|
||||||
err := ui.dispatch("ApproveNewAccount", request, &result)
|
err := ui.dispatch("ui_approveNewAccount", request, &result)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *StdIOUI) ShowError(message string) {
|
func (ui *StdIOUI) ShowError(message string) {
|
||||||
err := ui.notify("ShowError", &Message{message})
|
err := ui.notify("ui_showError", &Message{message})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error calling 'ShowError'", "exc", err.Error(), "msg", message)
|
log.Info("Error calling 'ui_showError'", "exc", err.Error(), "msg", message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *StdIOUI) ShowInfo(message string) {
|
func (ui *StdIOUI) ShowInfo(message string) {
|
||||||
err := ui.notify("ShowInfo", Message{message})
|
err := ui.notify("ui_showInfo", Message{message})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message)
|
log.Info("Error calling 'ui_showInfo'", "exc", err.Error(), "msg", message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
||||||
err := ui.notify("OnApprovedTx", tx)
|
err := ui.notify("ui_onApprovedTx", tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx)
|
log.Info("Error calling 'ui_onApprovedTx'", "exc", err.Error(), "tx", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *StdIOUI) OnSignerStartup(info StartupInfo) {
|
func (ui *StdIOUI) OnSignerStartup(info StartupInfo) {
|
||||||
err := ui.notify("OnSignerStartup", info)
|
err := ui.notify("ui_onSignerStartup", info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error calling 'OnSignerStartup'", "exc", err.Error(), "info", info)
|
log.Info("Error calling 'ui_onSignerStartup'", "exc", err.Error(), "info", info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (ui *StdIOUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
|
func (ui *StdIOUI) OnInputRequired(info UserInputRequest) (UserInputResponse, error) {
|
||||||
var result UserInputResponse
|
var result UserInputResponse
|
||||||
err := ui.dispatch("OnInputRequired", info, &result)
|
err := ui.dispatch("ui_onInputRequired", info, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Error calling 'OnInputRequired'", "exc", err.Error(), "info", info)
|
log.Info("Error calling 'ui_onInputRequired'", "exc", err.Error(), "info", info)
|
||||||
}
|
}
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"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/signer/core"
|
"github.com/ethereum/go-ethereum/signer/core"
|
||||||
@ -42,25 +41,23 @@ func consoleOutput(call otto.FunctionCall) otto.Value {
|
|||||||
for _, argument := range call.ArgumentList {
|
for _, argument := range call.ArgumentList {
|
||||||
output = append(output, fmt.Sprintf("%v", argument))
|
output = append(output, fmt.Sprintf("%v", argument))
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stdout, strings.Join(output, " "))
|
fmt.Fprintln(os.Stderr, strings.Join(output, " "))
|
||||||
return otto.Value{}
|
return otto.Value{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rulesetUI provides an implementation of UIClientAPI that evaluates a javascript
|
// rulesetUI provides an implementation of UIClientAPI that evaluates a javascript
|
||||||
// file for each defined UI-method
|
// file for each defined UI-method
|
||||||
type rulesetUI struct {
|
type rulesetUI struct {
|
||||||
next core.UIClientAPI // The next handler, for manual processing
|
next core.UIClientAPI // The next handler, for manual processing
|
||||||
storage storage.Storage
|
storage storage.Storage
|
||||||
credentials storage.Storage
|
jsRules string // The rules to use
|
||||||
jsRules string // The rules to use
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuleEvaluator(next core.UIClientAPI, jsbackend, credentialsBackend storage.Storage) (*rulesetUI, error) {
|
func NewRuleEvaluator(next core.UIClientAPI, jsbackend storage.Storage) (*rulesetUI, error) {
|
||||||
c := &rulesetUI{
|
c := &rulesetUI{
|
||||||
next: next,
|
next: next,
|
||||||
storage: jsbackend,
|
storage: jsbackend,
|
||||||
credentials: credentialsBackend,
|
jsRules: "",
|
||||||
jsRules: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
@ -153,18 +150,12 @@ func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse,
|
|||||||
if approved {
|
if approved {
|
||||||
return core.SignTxResponse{
|
return core.SignTxResponse{
|
||||||
Transaction: request.Transaction,
|
Transaction: request.Transaction,
|
||||||
Approved: true,
|
Approved: true},
|
||||||
Password: r.lookupPassword(request.Transaction.From.Address()),
|
|
||||||
},
|
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
return core.SignTxResponse{Approved: false}, err
|
return core.SignTxResponse{Approved: false}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rulesetUI) lookupPassword(address common.Address) string {
|
|
||||||
return r.credentials.Get(strings.ToLower(address.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
||||||
jsonreq, err := json.Marshal(request)
|
jsonreq, err := json.Marshal(request)
|
||||||
approved, err := r.checkApproval("ApproveSignData", jsonreq, err)
|
approved, err := r.checkApproval("ApproveSignData", jsonreq, err)
|
||||||
@ -173,28 +164,9 @@ func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDat
|
|||||||
return r.next.ApproveSignData(request)
|
return r.next.ApproveSignData(request)
|
||||||
}
|
}
|
||||||
if approved {
|
if approved {
|
||||||
return core.SignDataResponse{Approved: true, Password: r.lookupPassword(request.Address.Address())}, nil
|
return core.SignDataResponse{Approved: true}, nil
|
||||||
}
|
}
|
||||||
return core.SignDataResponse{Approved: false, Password: ""}, err
|
return core.SignDataResponse{Approved: false}, err
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rulesetUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
||||||
jsonreq, err := json.Marshal(request)
|
|
||||||
approved, err := r.checkApproval("ApproveExport", jsonreq, err)
|
|
||||||
if err != nil {
|
|
||||||
log.Info("Rule-based approval error, going to manual", "error", err)
|
|
||||||
return r.next.ApproveExport(request)
|
|
||||||
}
|
|
||||||
if approved {
|
|
||||||
return core.ExportResponse{Approved: true}, nil
|
|
||||||
}
|
|
||||||
return core.ExportResponse{Approved: false}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rulesetUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
||||||
// This cannot be handled by rules, requires setting a password
|
|
||||||
// dispatch to next
|
|
||||||
return r.next.ApproveImport(request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnInputRequired not handled by rules
|
// OnInputRequired not handled by rules
|
||||||
|
@ -84,19 +84,11 @@ func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
|
func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
|
||||||
return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil
|
return core.SignTxResponse{Transaction: request.Transaction, Approved: false}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
|
||||||
return core.SignDataResponse{Approved: false, Password: ""}, nil
|
return core.SignDataResponse{Approved: false}, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
||||||
return core.ExportResponse{Approved: false}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
||||||
return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
||||||
@ -104,7 +96,7 @@ func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
|
func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
|
||||||
return core.NewAccountResponse{Approved: false, Password: ""}, nil
|
return core.NewAccountResponse{Approved: false}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (alwaysDenyUI) ShowError(message string) {
|
func (alwaysDenyUI) ShowError(message string) {
|
||||||
@ -120,7 +112,7 @@ func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initRuleEngine(js string) (*rulesetUI, error) {
|
func initRuleEngine(js string) (*rulesetUI, error) {
|
||||||
r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
|
r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create js engine: %v", err)
|
return nil, fmt.Errorf("failed to create js engine: %v", err)
|
||||||
}
|
}
|
||||||
@ -225,16 +217,6 @@ func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataR
|
|||||||
return core.SignDataResponse{}, core.ErrRequestDenied
|
return core.SignDataResponse{}, core.ErrRequestDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
||||||
d.calls = append(d.calls, "ApproveExport")
|
|
||||||
return core.ExportResponse{}, core.ErrRequestDenied
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
||||||
d.calls = append(d.calls, "ApproveImport")
|
|
||||||
return core.ImportResponse{}, core.ErrRequestDenied
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
||||||
d.calls = append(d.calls, "ApproveListing")
|
d.calls = append(d.calls, "ApproveListing")
|
||||||
return core.ListResponse{}, core.ErrRequestDenied
|
return core.ListResponse{}, core.ErrRequestDenied
|
||||||
@ -266,8 +248,7 @@ func TestForwarding(t *testing.T) {
|
|||||||
js := ""
|
js := ""
|
||||||
ui := &dummyUI{make([]string, 0)}
|
ui := &dummyUI{make([]string, 0)}
|
||||||
jsBackend := storage.NewEphemeralStorage()
|
jsBackend := storage.NewEphemeralStorage()
|
||||||
credBackend := storage.NewEphemeralStorage()
|
r, err := NewRuleEvaluator(ui, jsBackend)
|
||||||
r, err := NewRuleEvaluator(ui, jsBackend, credBackend)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create js engine: %v", err)
|
t.Fatalf("Failed to create js engine: %v", err)
|
||||||
}
|
}
|
||||||
@ -276,17 +257,15 @@ func TestForwarding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
r.ApproveSignData(nil)
|
r.ApproveSignData(nil)
|
||||||
r.ApproveTx(nil)
|
r.ApproveTx(nil)
|
||||||
r.ApproveImport(nil)
|
|
||||||
r.ApproveNewAccount(nil)
|
r.ApproveNewAccount(nil)
|
||||||
r.ApproveListing(nil)
|
r.ApproveListing(nil)
|
||||||
r.ApproveExport(nil)
|
|
||||||
r.ShowError("test")
|
r.ShowError("test")
|
||||||
r.ShowInfo("test")
|
r.ShowInfo("test")
|
||||||
|
|
||||||
//This one is not forwarded
|
//This one is not forwarded
|
||||||
r.OnApprovedTx(ethapi.SignTransactionResult{})
|
r.OnApprovedTx(ethapi.SignTransactionResult{})
|
||||||
|
|
||||||
expCalls := 8
|
expCalls := 6
|
||||||
if len(ui.calls) != expCalls {
|
if len(ui.calls) != expCalls {
|
||||||
|
|
||||||
t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ","))
|
t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ","))
|
||||||
@ -545,16 +524,6 @@ func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDa
|
|||||||
return core.SignDataResponse{}, core.ErrRequestDenied
|
return core.SignDataResponse{}, core.ErrRequestDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) {
|
|
||||||
d.t.Fatalf("Did not expect next-handler to be called")
|
|
||||||
return core.ExportResponse{}, core.ErrRequestDenied
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) {
|
|
||||||
d.t.Fatalf("Did not expect next-handler to be called")
|
|
||||||
return core.ImportResponse{}, core.ErrRequestDenied
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
|
||||||
d.t.Fatalf("Did not expect next-handler to be called")
|
d.t.Fatalf("Did not expect next-handler to be called")
|
||||||
return core.ListResponse{}, core.ErrRequestDenied
|
return core.ListResponse{}, core.ErrRequestDenied
|
||||||
@ -597,7 +566,7 @@ func TestContextIsCleared(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
ui := &dontCallMe{t}
|
ui := &dontCallMe{t}
|
||||||
r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage())
|
r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create js engine: %v", err)
|
t.Fatalf("Failed to create js engine: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,6 @@
|
|||||||
|
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
// Put stores a value by key. 0-length keys results in no-op
|
// Put stores a value by key. 0-length keys results in no-op
|
||||||
Put(key, value string)
|
Put(key, value string)
|
||||||
@ -39,7 +35,7 @@ func (s *EphemeralStorage) Put(key, value string) {
|
|||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("storage: put %v -> %v\n", key, value)
|
//fmt.Printf("storage: put %v -> %v\n", key, value)
|
||||||
s.data[key] = value
|
s.data[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +43,7 @@ func (s *EphemeralStorage) Get(key string) string {
|
|||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
fmt.Printf("storage: get %v\n", key)
|
//fmt.Printf("storage: get %v\n", key)
|
||||||
if v, exist := s.data[key]; exist {
|
if v, exist := s.data[key]; exist {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
@ -60,3 +56,11 @@ func NewEphemeralStorage() Storage {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoStorage is a dummy construct which doesn't remember anything you tell it
|
||||||
|
type NoStorage struct{}
|
||||||
|
|
||||||
|
func (s *NoStorage) Put(key, value string) {}
|
||||||
|
func (s *NoStorage) Get(key string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user