github.com/daeglee/go-ethereum@v0.0.0-20190504220456-cad3e8d18e9b/cmd/clef/README.md (about) 1 Clef 2 ---- 3 Clef can be used to sign transactions and data and is meant as a replacement for geth's account management. 4 This allows DApps not to depend on geth's account management. When a DApp wants to sign data it can send the data to 5 the signer, the signer will then provide the user with context and asks the user for permission to sign the data. If 6 the users grants the signing request the signer will send the signature back to the DApp. 7 8 This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can 9 help in situations when a DApp is connected to a remote node because a local Ethereum node is not available, not 10 synchronised with the chain or a particular Ethereum node that has no built-in (or limited) account management. 11 12 Clef can run as a daemon on the same machine, or off a usb-stick like [usb armory](https://inversepath.com/usbarmory), 13 or a separate VM in a [QubesOS](https://www.qubes-os.org/) type os setup. 14 15 Check out 16 17 * the [tutorial](tutorial.md) for some concrete examples on how the signer works. 18 * the [setup docs](docs/setup.md) for some information on how to configure it to work on QubesOS or USBArmory. 19 * the [data types](datatypes.md) for detailed information on the json types used in the communication between 20 clef and an external UI 21 22 ## Command line flags 23 Clef accepts the following command line options: 24 ``` 25 COMMANDS: 26 init Initialize the signer, generate secret storage 27 attest Attest that a js-file is to be used 28 setpw Store a credential for a keystore file 29 gendoc Generate documentation about json-rpc format 30 help Shows a list of commands or help for one command 31 32 GLOBAL OPTIONS: 33 --loglevel value log level to emit to the screen (default: 4) 34 --keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore") 35 --configdir value Directory for Clef configuration (default: "$HOME/.clef") 36 --chainid value Chain id to use for signing (1=mainnet, 3=ropsten, 4=rinkeby, 5=Goerli) (default: 1) 37 --lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength 38 --nousb Disables monitoring for and managing USB hardware wallets 39 --rpcaddr value HTTP-RPC server listening interface (default: "localhost") 40 --rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost") 41 --ipcdisable Disable the IPC-RPC server 42 --ipcpath Filename for IPC socket/pipe within the datadir (explicit paths escape it) 43 --rpc Enable the HTTP-RPC server 44 --rpcport value HTTP-RPC server listening port (default: 8550) 45 --signersecret value A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash 46 --4bytedb value File containing 4byte-identifiers (default: "./4byte.json") 47 --4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json") 48 --auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log") 49 --rules value Enable rule-engine (default: "rules.json") 50 --stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when Clef is started by an external process. 51 --stdio-ui-test Mechanism to test interface between Clef and UI. Requires 'stdio-ui'. 52 --advanced If enabled, issues warnings instead of rejections for suspicious requests. Default off 53 --help, -h show help 54 --version, -v print the version 55 56 ``` 57 58 59 Example: 60 ``` 61 signer -keystore /my/keystore -chainid 4 62 ``` 63 64 65 ## Security model 66 67 The security model of the signer is as follows: 68 69 * One critical component (the signer binary / daemon) is responsible for handling cryptographic operations: signing, private keys, encryption/decryption of keystore files. 70 * The signer binary has a well-defined 'external' API. 71 * The 'external' API is considered UNTRUSTED. 72 * The signer binary also communicates with whatever process that invoked the binary, via stdin/stdout. 73 * This channel is considered 'trusted'. Over this channel, approvals and passwords are communicated. 74 75 The general flow for signing a transaction using e.g. geth is as follows: 76  77 78 In this case, `geth` would be started with `--externalsigner=http://localhost:8550` and would relay requests to `eth.sendTransaction`. 79 80 ## TODOs 81 82 Some snags and todos 83 84 * [ ] The signer should take a startup param "--no-change", for UIs that do not contain the capability 85 to perform changes to things, only approve/deny. Such a UI should be able to start the signer in 86 a more secure mode by telling it that it only wants approve/deny capabilities. 87 88 * [x] It would be nice if the signer could collect new 4byte-id:s/method selectors, and have a 89 secondary database for those (`4byte_custom.json`). Users could then (optionally) submit their collections for 90 inclusion upstream. 91 92 * It should be possible to configure the signer to check if an account is indeed known to it, before 93 passing on to the UI. The reason it currently does not, is that it would make it possible to enumerate 94 accounts if it immediately returned "unknown account". 95 * [x] It should be possible to configure the signer to auto-allow listing (certain) accounts, instead of asking every time. 96 * [x] Done Upon startup, the signer should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode), 97 invoking methods with the following info: 98 * [x] Version info about the signer 99 * [x] Address of API (http/ipc) 100 * [ ] List of known accounts 101 * [ ] Have a default timeout on signing operations, so that if the user has not answered within e.g. 60 seconds, the request is rejected. 102 * [ ] `account_signRawTransaction` 103 * [ ] `account_bulkSignTransactions([] transactions)` should 104 * only exist if enabled via config/flag 105 * only allow non-data-sending transactions 106 * all txs must use the same `from`-account 107 * let the user confirm, showing 108 * the total amount 109 * the number of unique recipients 110 111 * Geth todos 112 - The signer should pass the `Origin` header as call-info to the UI. As of right now, the way that info about the request is 113 put together is a bit of a hack into the http server. This could probably be greatly improved 114 - Relay: Geth should be started in `geth --external_signer localhost:8550`. 115 - Currently, the Geth APIs use `common.Address` in the arguments to transaction submission (e.g `to` field). This 116 type is 20 `bytes`, and is incapable of carrying checksum information. The signer uses `common.MixedcaseAddress`, which 117 retains the original input. 118 - The Geth api should switch to use the same type, and relay `to`-account verbatim to the external api. 119 120 * [x] Storage 121 * [x] An encrypted key-value storage should be implemented 122 * See [rules.md](rules.md) for more info about this. 123 124 * Another potential thing to introduce is pairing. 125 * To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API). 126 * Thus geth/mist/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests. 127 * This feature would make the addition of rules less dangerous. 128 129 * Wallets / accounts. Add API methods for wallets. 130 131 ## Communication 132 133 ### External API 134 135 The signer listens to HTTP requests on `rpcaddr`:`rpcport`, with the same JSONRPC standard as Geth. The messages are 136 expected to be JSON [jsonrpc 2.0 standard](http://www.jsonrpc.org/specification). 137 138 Some of these call can require user interaction. Clients must be aware that responses 139 may be delayed significantly or may never be received if a users decides to ignore the confirmation request. 140 141 The External API is **untrusted** : it does not accept credentials over this api, nor does it expect 142 that requests have any authority. 143 144 ### UI API 145 146 The signer has one native console-based UI, for operation without any standalone tools. 147 However, there is also an API to communicate with an external UI. To enable that UI, 148 the signer needs to be executed with the `--stdio-ui` option, which allocates the 149 `stdin`/`stdout` for the UI-api. 150 151 An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`. 152 153 The model is as follows: 154 155 * The user starts the UI app (`pythonsigner.py`). 156 * The UI app starts the `signer` with `--stdio-ui`, and listens to the 157 process output for confirmation-requests. 158 * The `signer` opens the external http api. 159 * When the `signer` receives requests, it sends a `jsonrpc` request via `stdout`. 160 * The UI app prompts the user accordingly, and responds to the `signer` 161 * The `signer` signs (or not), and responds to the original request. 162 163 ## External API 164 165 See the [external api changelog](extapi_changelog.md) for information about changes to this API. 166 167 ### Encoding 168 - number: positive integers that are hex encoded 169 - data: hex encoded data 170 - string: ASCII string 171 172 All hex encoded values must be prefixed with `0x`. 173 174 ## Methods 175 176 ### account_new 177 178 #### Create new password protected account 179 180 The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory. 181 The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts. 182 183 #### Arguments 184 185 None 186 187 #### Result 188 - address [string]: account address that is derived from the generated key 189 - url [string]: location of the keyfile 190 191 #### Sample call 192 ```json 193 { 194 "id": 0, 195 "jsonrpc": "2.0", 196 "method": "account_new", 197 "params": [] 198 } 199 ``` 200 Response 201 ``` 202 { 203 "id": 0, 204 "jsonrpc": "2.0", 205 "result": { 206 "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", 207 "url": "keystore:///my/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" 208 } 209 } 210 ``` 211 212 ### account_list 213 214 #### List available accounts 215 List all accounts that this signer currently manages 216 217 #### Arguments 218 219 None 220 221 #### Result 222 - array with account records: 223 - account.address [string]: account address that is derived from the generated key 224 - account.type [string]: type of the 225 - account.url [string]: location of the account 226 227 #### Sample call 228 ```json 229 { 230 "id": 1, 231 "jsonrpc": "2.0", 232 "method": "account_list" 233 } 234 ``` 235 Response 236 ``` 237 { 238 "id": 1, 239 "jsonrpc": "2.0", 240 "result": [ 241 { 242 "address": "0xafb2f771f58513609765698f65d3f2f0224a956f", 243 "type": "account", 244 "url": "keystore:///tmp/keystore/UTC--2017-08-24T07-26-47.162109726Z--afb2f771f58513609765698f65d3f2f0224a956f" 245 }, 246 { 247 "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", 248 "type": "account", 249 "url": "keystore:///tmp/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" 250 } 251 ] 252 } 253 ``` 254 255 ### account_signTransaction 256 257 #### Sign transactions 258 Signs a transactions and responds with the signed transaction in RLP encoded form. 259 260 #### Arguments 261 2. transaction object: 262 - `from` [address]: account to send the transaction from 263 - `to` [address]: receiver account. If omitted or `0x`, will cause contract creation. 264 - `gas` [number]: maximum amount of gas to burn 265 - `gasPrice` [number]: gas price 266 - `value` [number:optional]: amount of Wei to send with the transaction 267 - `data` [data:optional]: input data 268 - `nonce` [number]: account nonce 269 3. method signature [string:optional] 270 - The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected. 271 272 273 #### Result 274 - signed transaction in RLP encoded form [data] 275 276 #### Sample call 277 ```json 278 { 279 "id": 2, 280 "jsonrpc": "2.0", 281 "method": "account_signTransaction", 282 "params": [ 283 { 284 "from": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", 285 "gas": "0x55555", 286 "gasPrice": "0x1234", 287 "input": "0xabcd", 288 "nonce": "0x0", 289 "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", 290 "value": "0x1234" 291 } 292 ] 293 } 294 ``` 295 Response 296 297 ```json 298 { 299 "id": 2, 300 "jsonrpc": "2.0", 301 "error": { 302 "code": -32000, 303 "message": "Request denied" 304 } 305 } 306 ``` 307 #### Sample call with ABI-data 308 309 310 ```json 311 { 312 "id": 67, 313 "jsonrpc": "2.0", 314 "method": "account_signTransaction", 315 "params": [ 316 { 317 "from": "0x694267f14675d7e1b9494fd8d72fefe1755710fa", 318 "gas": "0x333", 319 "gasPrice": "0x1", 320 "nonce": "0x0", 321 "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", 322 "value": "0x0", 323 "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" 324 }, 325 "safeSend(address)" 326 ] 327 } 328 ``` 329 Response 330 331 ```json 332 { 333 "jsonrpc": "2.0", 334 "id": 67, 335 "result": { 336 "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", 337 "tx": { 338 "nonce": "0x0", 339 "gasPrice": "0x1", 340 "gas": "0x333", 341 "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", 342 "value": "0x0", 343 "input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", 344 "v": "0x26", 345 "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", 346 "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", 347 "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" 348 } 349 } 350 } 351 ``` 352 353 Bash example: 354 ```bash 355 #curl -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/ 356 357 {"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} 358 ``` 359 360 ### account_signData 361 362 #### Sign data 363 Signs a chunk of data and returns the calculated signature. 364 365 #### Arguments 366 - content type [string]: type of signed data 367 - `text/validator`: hex data with custom validator defined in a contract 368 - `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers 369 - `text/plain`: simple hex data validated by `account_ecRecover` 370 - account [address]: account to sign with 371 - data [object]: data to sign 372 373 #### Result 374 - calculated signature [data] 375 376 #### Sample call 377 ```json 378 { 379 "id": 3, 380 "jsonrpc": "2.0", 381 "method": "account_signData", 382 "params": [ 383 "data/plain", 384 "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", 385 "0xaabbccdd" 386 ] 387 } 388 ``` 389 Response 390 391 ```json 392 { 393 "id": 3, 394 "jsonrpc": "2.0", 395 "result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" 396 } 397 ``` 398 399 ### account_signTypedData 400 401 #### Sign data 402 Signs a chunk of structured data conformant to [EIP712]([EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md)) and returns the calculated signature. 403 404 #### Arguments 405 - account [address]: account to sign with 406 - data [object]: data to sign 407 408 #### Result 409 - calculated signature [data] 410 411 #### Sample call 412 ```json 413 { 414 "id": 68, 415 "jsonrpc": "2.0", 416 "method": "account_signTypedData", 417 "params": [ 418 "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", 419 { 420 "types": { 421 "EIP712Domain": [ 422 { 423 "name": "name", 424 "type": "string" 425 }, 426 { 427 "name": "version", 428 "type": "string" 429 }, 430 { 431 "name": "chainId", 432 "type": "uint256" 433 }, 434 { 435 "name": "verifyingContract", 436 "type": "address" 437 } 438 ], 439 "Person": [ 440 { 441 "name": "name", 442 "type": "string" 443 }, 444 { 445 "name": "wallet", 446 "type": "address" 447 } 448 ], 449 "Mail": [ 450 { 451 "name": "from", 452 "type": "Person" 453 }, 454 { 455 "name": "to", 456 "type": "Person" 457 }, 458 { 459 "name": "contents", 460 "type": "string" 461 } 462 ] 463 }, 464 "primaryType": "Mail", 465 "domain": { 466 "name": "Ether Mail", 467 "version": "1", 468 "chainId": 1, 469 "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" 470 }, 471 "message": { 472 "from": { 473 "name": "Cow", 474 "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" 475 }, 476 "to": { 477 "name": "Bob", 478 "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" 479 }, 480 "contents": "Hello, Bob!" 481 } 482 } 483 ] 484 } 485 ``` 486 Response 487 488 ```json 489 { 490 "id": 1, 491 "jsonrpc": "2.0", 492 "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c" 493 } 494 ``` 495 496 ### account_ecRecover 497 498 #### Sign data 499 500 Derive the address from the account that was used to sign data with content type `text/plain` and the signature. 501 502 #### Arguments 503 - data [data]: data that was signed 504 - signature [data]: the signature to verify 505 506 #### Result 507 - derived account [address] 508 509 #### Sample call 510 ```json 511 { 512 "id": 4, 513 "jsonrpc": "2.0", 514 "method": "account_ecRecover", 515 "params": [ 516 "data/plain", 517 "0xaabbccdd", 518 "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" 519 ] 520 } 521 ``` 522 Response 523 524 ```json 525 { 526 "id": 4, 527 "jsonrpc": "2.0", 528 "result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db" 529 } 530 ``` 531 532 ### account_import 533 534 #### Import account 535 Import a private key into the keystore. The imported key is expected to be encrypted according to the web3 keystore 536 format. 537 538 #### Arguments 539 - account [object]: key in [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) (retrieved with account_export) 540 541 #### Result 542 - imported key [object]: 543 - key.address [address]: address of the imported key 544 - key.type [string]: type of the account 545 - key.url [string]: key URL 546 547 #### Sample call 548 ```json 549 { 550 "id": 6, 551 "jsonrpc": "2.0", 552 "method": "account_import", 553 "params": [ 554 { 555 "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", 556 "crypto": { 557 "cipher": "aes-128-ctr", 558 "cipherparams": { 559 "iv": "401c39a7c7af0388491c3d3ecb39f532" 560 }, 561 "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", 562 "kdf": "scrypt", 563 "kdfparams": { 564 "dklen": 32, 565 "n": 262144, 566 "p": 1, 567 "r": 8, 568 "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" 569 }, 570 "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" 571 }, 572 "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", 573 "version": 3 574 } 575 ] 576 } 577 ``` 578 Response 579 580 ```json 581 { 582 "id": 6, 583 "jsonrpc": "2.0", 584 "result": { 585 "address": "0xc7412fc59930fd90099c917a50e5f11d0934b2f5", 586 "type": "account", 587 "url": "keystore:///tmp/keystore/UTC--2017-08-24T11-00-42.032024108Z--c7412fc59930fd90099c917a50e5f11d0934b2f5" 588 } 589 } 590 ``` 591 592 ### account_export 593 594 #### Export account from keystore 595 Export a private key from the keystore. The exported private key is encrypted with the original passphrase. When the 596 key is imported later this passphrase is required. 597 598 #### Arguments 599 - account [address]: export private key that is associated with this account 600 601 #### Result 602 - exported key, see [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for 603 more information 604 605 #### Sample call 606 ```json 607 { 608 "id": 5, 609 "jsonrpc": "2.0", 610 "method": "account_export", 611 "params": [ 612 "0xc7412fc59930fd90099c917a50e5f11d0934b2f5" 613 ] 614 } 615 ``` 616 Response 617 618 ```json 619 { 620 "id": 5, 621 "jsonrpc": "2.0", 622 "result": { 623 "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", 624 "crypto": { 625 "cipher": "aes-128-ctr", 626 "cipherparams": { 627 "iv": "401c39a7c7af0388491c3d3ecb39f532" 628 }, 629 "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", 630 "kdf": "scrypt", 631 "kdfparams": { 632 "dklen": 32, 633 "n": 262144, 634 "p": 1, 635 "r": 8, 636 "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" 637 }, 638 "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" 639 }, 640 "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", 641 "version": 3 642 } 643 } 644 ``` 645 646 647 648 ## UI API 649 650 These methods needs to be implemented by a UI listener. 651 652 By starting the signer with the switch `--stdio-ui-test`, the signer will invoke all known methods, and expect the UI to respond with 653 denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented. 654 See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'. 655 656 All methods in this API uses object-based parameters, so that there can be no mixups of parameters: each piece of data is accessed by key. 657 658 See the [ui api changelog](intapi_changelog.md) for information about changes to this API. 659 660 OBS! A slight deviation from `json` standard is in place: every request and response should be confined to a single line. 661 Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make 662 things simpler for both parties. 663 664 ### ApproveTx / `ui_approveTx` 665 666 Invoked when there's a transaction for approval. 667 668 669 #### Sample call 670 671 Here's a method invocation: 672 ```bash 673 674 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/ 675 ``` 676 Results in the following invocation on the UI: 677 ```json 678 679 { 680 "jsonrpc": "2.0", 681 "id": 1, 682 "method": "ui_approveTx", 683 "params": [ 684 { 685 "transaction": { 686 "from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa", 687 "to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0", 688 "gas": "0x333", 689 "gasPrice": "0x1", 690 "value": "0x0", 691 "nonce": "0x0", 692 "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", 693 "input": null 694 }, 695 "call_info": [ 696 { 697 "type": "WARNING", 698 "message": "Invalid checksum on to-address" 699 }, 700 { 701 "type": "Info", 702 "message": "safeSend(address: 0x0000000000000000000000000000000000000012)" 703 } 704 ], 705 "meta": { 706 "remote": "127.0.0.1:48486", 707 "local": "localhost:8550", 708 "scheme": "HTTP/1.1" 709 } 710 } 711 ] 712 } 713 714 ``` 715 716 The same method invocation, but with invalid data: 717 ```bash 718 719 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":"0x4401a6e40000000000000002000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ 720 ``` 721 722 ```json 723 724 { 725 "jsonrpc": "2.0", 726 "id": 1, 727 "method": "ui_approveTx", 728 "params": [ 729 { 730 "transaction": { 731 "from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa", 732 "to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0", 733 "gas": "0x333", 734 "gasPrice": "0x1", 735 "value": "0x0", 736 "nonce": "0x0", 737 "data": "0x4401a6e40000000000000002000000000000000000000000000000000000000000000012", 738 "input": null 739 }, 740 "call_info": [ 741 { 742 "type": "WARNING", 743 "message": "Invalid checksum on to-address" 744 }, 745 { 746 "type": "WARNING", 747 "message": "Transaction data did not match ABI-interface: WARNING: Supplied data is stuffed with extra data. \nWant 0000000000000002000000000000000000000000000000000000000000000012\nHave 0000000000000000000000000000000000000000000000000000000000000012\nfor method safeSend(address)" 748 } 749 ], 750 "meta": { 751 "remote": "127.0.0.1:48492", 752 "local": "localhost:8550", 753 "scheme": "HTTP/1.1" 754 } 755 } 756 ] 757 } 758 759 760 ``` 761 762 One which has missing `to`, but with no `data`: 763 764 765 ```json 766 767 { 768 "jsonrpc": "2.0", 769 "id": 3, 770 "method": "ui_approveTx", 771 "params": [ 772 { 773 "transaction": { 774 "from": "", 775 "to": null, 776 "gas": "0x0", 777 "gasPrice": "0x0", 778 "value": "0x0", 779 "nonce": "0x0", 780 "data": null, 781 "input": null 782 }, 783 "call_info": [ 784 { 785 "type": "CRITICAL", 786 "message": "Tx will create contract with empty code!" 787 } 788 ], 789 "meta": { 790 "remote": "signer binary", 791 "local": "main", 792 "scheme": "in-proc" 793 } 794 } 795 ] 796 } 797 ``` 798 799 ### ApproveListing / `ui_approveListing` 800 801 Invoked when a request for account listing has been made. 802 803 #### Sample call 804 805 ```json 806 807 { 808 "jsonrpc": "2.0", 809 "id": 5, 810 "method": "ui_approveListing", 811 "params": [ 812 { 813 "accounts": [ 814 { 815 "type": "Account", 816 "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42", 817 "address": "0x123409812340981234098123409812deadbeef42" 818 }, 819 { 820 "type": "Account", 821 "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42", 822 "address": "0xcafebabedeadbeef34098123409812deadbeef42" 823 } 824 ], 825 "meta": { 826 "remote": "signer binary", 827 "local": "main", 828 "scheme": "in-proc" 829 } 830 } 831 ] 832 } 833 834 ``` 835 836 837 ### ApproveSignData / `ui_approveSignData` 838 839 #### Sample call 840 841 ```json 842 { 843 "jsonrpc": "2.0", 844 "id": 4, 845 "method": "ui_approveSignData", 846 "params": [ 847 { 848 "address": "0x123409812340981234098123409812deadbeef42", 849 "raw_data": "0x01020304", 850 "message": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", 851 "hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310", 852 "meta": { 853 "remote": "signer binary", 854 "local": "main", 855 "scheme": "in-proc" 856 } 857 } 858 ] 859 } 860 861 ``` 862 863 ### ShowInfo / `ui_showInfo` 864 865 The UI should show the info to the user. Does not expect response. 866 867 #### Sample call 868 869 ```json 870 { 871 "jsonrpc": "2.0", 872 "id": 9, 873 "method": "ui_showInfo", 874 "params": [ 875 { 876 "text": "Tests completed" 877 } 878 ] 879 } 880 881 ``` 882 883 ### ShowError / `ui_showError` 884 885 The UI should show the info to the user. Does not expect response. 886 887 ```json 888 889 { 890 "jsonrpc": "2.0", 891 "id": 2, 892 "method": "ShowError", 893 "params": [ 894 { 895 "text": "Testing 'ShowError'" 896 } 897 ] 898 } 899 900 ``` 901 902 ### OnApprovedTx / `ui_onApprovedTx` 903 904 `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. 905 906 When implementing rate-limited rules, this callback should be used. 907 908 TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`. 909 910 ### OnSignerStartup / `ui_onSignerStartup` 911 912 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, 913 in k/v-form. 914 915 Example call: 916 ```json 917 918 { 919 "jsonrpc": "2.0", 920 "id": 1, 921 "method": "ui_onSignerStartup", 922 "params": [ 923 { 924 "info": { 925 "extapi_http": "http://localhost:8550", 926 "extapi_ipc": null, 927 "extapi_version": "2.0.0", 928 "intapi_version": "1.2.0" 929 } 930 } 931 ] 932 } 933 934 ``` 935 936 937 ### Rules for UI apis 938 939 A UI should conform to the following rules. 940 941 * A UI MUST NOT load any external resources that were not embedded/part of the UI package. 942 * For example, not load icons, stylesheets from the internet 943 * Not load files from the filesystem, unless they reside in the same local directory (e.g. config files) 944 * A Graphical UI MUST show the blocky-identicon for ethereum addresses. 945 * A UI MUST warn display approproate warning if the destination-account is formatted with invalid checksum. 946 * A UI MUST NOT open any ports or services 947 * The signer opens the public port 948 * A UI SHOULD verify the permissions on the signer binary, and refuse to execute or warn if permissions allow non-user write. 949 * A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed 950 * A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts 951 * The signer provides accounts 952 * A UI SHOULD, to the best extent possible, use static linking / bundling, so that required libraries are bundled 953 along with the UI. 954 955 956 ### UI Implementations 957 958 There are a couple of implementation for a UI. We'll try to keep this list up to date. 959 960 | Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters| 961 | ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- | 962 | QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)| 963 | GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: | 964 | Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: | 965 | Clef UI| https://github.com/kyokan/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)|