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