github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/cmd/clef/tutorial.md (about) 1 ## Initializing Clef 2 3 First things first, Clef needs to store some data itself. Since that data might be sensitive (passwords, signing rules, accounts), Clef's entire storage is encrypted. To support encrypting data, the first step is to initialize Clef with a random master seed, itself too encrypted with your chosen password: 4 5 ```text 6 $ clef init 7 8 WARNING! 9 10 Clef is an account management tool. It may, like any software, contain bugs. 11 12 Please take care to 13 - backup your keystore files, 14 - verify that the keystore(s) can be opened with your password. 15 16 Clef is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 17 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 18 PURPOSE. See the GNU General Public License for more details. 19 20 Enter 'ok' to proceed: 21 > ok 22 23 The master seed of clef will be locked with a password. 24 Please specify a password. Do not forget this password! 25 Password: 26 Repeat password: 27 28 A master seed has been generated into /home/martin/.clef/masterseed.json 29 30 This is required to be able to store credentials, such as: 31 * Passwords for keystores (used by rule engine) 32 * Storage for JavaScript auto-signing rules 33 * Hash of JavaScript rule-file 34 35 You should treat 'masterseed.json' with utmost secrecy and make a backup of it! 36 * The password is necessary but not enough, you need to back up the master seed too! 37 * The master seed does not contain your accounts, those need to be backed up separately! 38 ``` 39 40 _For readability purposes, we'll remove the WARNING printout, user confirmation and the unlocking of the master seed in the rest of this document._ 41 42 ## Remote interactions 43 44 Clef is capable of managing both key-file based accounts as well as hardware wallets. To evaluate clef, we're going to point it to our Rinkeby testnet keystore and specify the Rinkeby chain ID for signing (Clef doesn't have a backing chain, so it doesn't know what network it runs on). 45 46 ```text 47 $ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 48 49 INFO [07-01|11:00:46.385] Starting signer chainid=4 keystore=$HOME/.ethereum/rinkeby/keystore light-kdf=false advanced=false 50 DEBUG[07-01|11:00:46.389] FS scan times list=3.521941ms set=9.017µs diff=4.112µs 51 DEBUG[07-01|11:00:46.391] Ledger support enabled 52 DEBUG[07-01|11:00:46.391] Trezor support enabled via HID 53 DEBUG[07-01|11:00:46.391] Trezor support enabled via WebUSB 54 INFO [07-01|11:00:46.391] Audit logs configured file=audit.log 55 DEBUG[07-01|11:00:46.392] IPC registered namespace=account 56 INFO [07-01|11:00:46.392] IPC endpoint opened url=$HOME/.clef/clef.ipc 57 ------- Signer info ------- 58 * intapi_version : 7.0.0 59 * extapi_version : 6.0.0 60 * extapi_http : n/a 61 * extapi_ipc : $HOME/.clef/clef.ipc 62 ``` 63 64 By default, Clef starts up in CLI (Command Line Interface) mode. Arbitrary remote processes may _request_ account interactions (e.g. sign a transaction), which the user will need to individually _confirm_. 65 66 To test this out, we can _request_ Clef to list all account via its _External API endpoint_: 67 68 ```text 69 echo '{"id": 1, "jsonrpc": "2.0", "method": "account_list"}' | nc -U ~/.clef/clef.ipc 70 ``` 71 72 This will prompt the user within the Clef CLI to confirm or deny the request: 73 74 ```text 75 -------- List Account request-------------- 76 A request has been made to list all accounts. 77 You can select which accounts the caller can see 78 [x] 0xD9C9Cd5f6779558b6e0eD4e6Acf6b1947E7fA1F3 79 URL: keystore://$HOME/.ethereum/rinkeby/keystore/UTC--2017-04-14T15-15-00.327614556Z--d9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 80 [x] 0x086278A6C067775F71d6B2BB1856Db6E28c30418 81 URL: keystore://$HOME/.ethereum/rinkeby/keystore/UTC--2018-02-06T22-53-11.211657239Z--086278a6c067775f71d6b2bb1856db6e28c30418 82 ------------------------------------------- 83 Request context: 84 NA -> NA -> NA 85 86 Additional HTTP header data, provided by the external caller: 87 User-Agent: 88 Origin: 89 Approve? [y/N]: 90 > 91 ``` 92 93 Depending on whether we approve or deny the request, the original NetCat process will get: 94 95 ```text 96 {"jsonrpc":"2.0","id":1,"result":["0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3","0x086278a6c067775f71d6b2bb1856db6e28c30418"]} 97 98 or 99 100 {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Request denied"}} 101 ``` 102 103 Apart from listing accounts, you can also _request_ creating a new account; signing transactions and data; and recovering signatures. You can find the available methods in the Clef [External API Spec](https://github.com/tacshi/go-ethereum/tree/master/cmd/clef#external-api-1) and the [External API Changelog](https://github.com/tacshi/go-ethereum/blob/master/cmd/clef/extapi_changelog.md). 104 105 _Note, the number of things you can do from the External API is deliberately small, since we want to limit the power of remote calls by as much as possible! Clef has an [Internal API](https://github.com/tacshi/go-ethereum/tree/master/cmd/clef#ui-api-1) too for the UI (User Interface) which is much richer and can support custom interfaces on top. But that's out of scope here._ 106 107 ## Automatic rules 108 109 For most users, manually confirming every transaction is the way to go. However, there are cases when it makes sense to set up some rules which permit Clef to sign a transaction without prompting the user. One such example would be running a signer on Rinkeby or other PoA networks. 110 111 For starters, we can create a rule file that automatically permits anyone to list our available accounts without user confirmation. The rule file is a tiny JavaScript snippet that you can program however you want: 112 113 ```js 114 function ApproveListing() { 115 return "Approve"; 116 } 117 ``` 118 119 Of course, Clef isn't going to just accept and run arbitrary scripts you give it, that would be dangerous if someone changes your rule file! Instead, you need to explicitly _attest_ the rule file, which entails injecting its hash into Clef's secure store. 120 121 ```text 122 $ sha256sum rules.js 123 645b58e4f945e24d0221714ff29f6aa8e860382ced43490529db1695f5fcc71c rules.js 124 125 $ clef attest 645b58e4f945e24d0221714ff29f6aa8e860382ced43490529db1695f5fcc71c 126 Decrypt master seed of clef 127 Password: 128 INFO [07-01|13:25:03.290] Ruleset attestation updated sha256=645b58e4f945e24d0221714ff29f6aa8e860382ced43490529db1695f5fcc71c 129 ``` 130 131 At this point, we can start Clef with the rule file: 132 133 ```text 134 $ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 --rules rules.js 135 136 INFO [07-01|13:39:49.726] Rule engine configured file=rules.js 137 INFO [07-01|13:39:49.726] Starting signer chainid=4 keystore=$HOME/.ethereum/rinkeby/keystore light-kdf=false advanced=false 138 DEBUG[07-01|13:39:49.726] FS scan times list=35.15µs set=4.251µs diff=2.766µs 139 DEBUG[07-01|13:39:49.727] Ledger support enabled 140 DEBUG[07-01|13:39:49.727] Trezor support enabled via HID 141 DEBUG[07-01|13:39:49.727] Trezor support enabled via WebUSB 142 INFO [07-01|13:39:49.728] Audit logs configured file=audit.log 143 DEBUG[07-01|13:39:49.728] IPC registered namespace=account 144 INFO [07-01|13:39:49.728] IPC endpoint opened url=$HOME/.clef/clef.ipc 145 ------- Signer info ------- 146 * intapi_version : 7.0.0 147 * extapi_version : 6.0.0 148 * extapi_http : n/a 149 * extapi_ipc : $HOME/.clef/clef.ipc 150 ``` 151 152 Any account listing _request_ will now be auto-approved by the rule file: 153 154 ```text 155 $ echo '{"id": 1, "jsonrpc": "2.0", "method": "account_list"}' | nc -U ~/.clef/clef.ipc 156 {"jsonrpc":"2.0","id":1,"result":["0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3","0x086278a6c067775f71d6b2bb1856db6e28c30418"]} 157 ``` 158 159 ## Under the hood 160 161 While doing the operations above, these files have been created: 162 163 ```text 164 $ ls -laR ~/.clef/ 165 166 $HOME/.clef/: 167 total 24 168 drwxr-x--x 3 user user 4096 Jul 1 13:45 . 169 drwxr-xr-x 102 user user 12288 Jul 1 13:39 .. 170 drwx------ 2 user user 4096 Jul 1 13:25 02f90c0603f4f2f60188 171 -r-------- 1 user user 868 Jun 28 13:55 masterseed.json 172 173 $HOME/.clef/02f90c0603f4f2f60188: 174 total 12 175 drwx------ 2 user user 4096 Jul 1 13:25 . 176 drwxr-x--x 3 user user 4096 Jul 1 13:45 .. 177 -rw------- 1 user user 159 Jul 1 13:25 config.json 178 179 $ cat ~/.clef/02f90c0603f4f2f60188/config.json 180 {"ruleset_sha256":{"iv":"SWWEtnl+R+I+wfG7","c":"I3fjmwmamxVcfGax7D0MdUOL29/rBWcs73WBILmYK0o1CrX7wSMc3y37KsmtlZUAjp0oItYq01Ow8VGUOzilG91tDHInB5YHNtm/YkufEbo="}} 181 ``` 182 183 In `$HOME/.clef`, the `masterseed.json` file was created, containing the master seed. This seed was then used to derive a few other things: 184 185 - **Vault location**: in this case `02f90c0603f4f2f60188`. 186 - If you use a different master seed, a different vault location will be used that does not conflict with each other (e.g. `clef --signersecret /path/to/file`). This allows you to run multiple instances of Clef, each with its own rules (e.g. mainnet + testnet). 187 - **`config.json`**: the encrypted key/value storage for configuration data, currently only containing the key `ruleset_sha256`, the attested hash of the automatic rules to use. 188 189 ## Advanced rules 190 191 In order to make more useful rules - like signing transactions - the signer needs access to the passwords needed to unlock keys from the keystore. You can inject an unlock password via `clef setpw`. 192 193 ```text 194 $ clef setpw 0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 195 196 Please enter a password to store for this address: 197 Password: 198 Repeat password: 199 200 Decrypt master seed of clef 201 Password: 202 INFO [07-01|14:05:56.031] Credential store updated key=0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 203 ``` 204 205 Now let's update the rules to make use of the new credentials: 206 207 ```js 208 function ApproveListing() { 209 return "Approve"; 210 } 211 212 function ApproveSignData(req) { 213 if ( 214 req.address.toLowerCase() == "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3" 215 ) { 216 if (req.messages[0].value.indexOf("bazonk") >= 0) { 217 return "Approve"; 218 } 219 return "Reject"; 220 } 221 // Otherwise goes to manual processing 222 } 223 ``` 224 225 In this example: 226 227 - Any requests to sign data with the account `0xd9c9...` will be: 228 - Auto-approved if the message contains `bazonk`, 229 - Auto-rejected if the message does not contain `bazonk`, 230 - Any other requests will be passed along for manual confirmation. 231 232 _Note, to make this example work, please use you own accounts. You can create a new account either via Clef or the traditional account CLI tools. If the latter was chosen, make sure both Clef and Geth use the same keystore by specifying `--keystore path/to/your/keystore` when running Clef._ 233 234 Attest the new rule file so that Clef will accept loading it: 235 236 ```text 237 $ sha256sum rules.js 238 f163a1738b649259bb9b369c593fdc4c6b6f86cc87e343c3ba58faee03c2a178 rules.js 239 240 $ clef attest f163a1738b649259bb9b369c593fdc4c6b6f86cc87e343c3ba58faee03c2a178 241 Decrypt master seed of clef 242 Password: 243 INFO [07-01|14:11:28.509] Ruleset attestation updated sha256=f163a1738b649259bb9b369c593fdc4c6b6f86cc87e343c3ba58faee03c2a178 244 ``` 245 246 Restart Clef with the new rules in place: 247 248 ``` 249 $ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 --rules rules.js 250 251 INFO [07-01|14:12:41.636] Rule engine configured file=rules.js 252 INFO [07-01|14:12:41.636] Starting signer chainid=4 keystore=$HOME/.ethereum/rinkeby/keystore light-kdf=false advanced=false 253 DEBUG[07-01|14:12:41.636] FS scan times list=46.722µs set=4.47µs diff=2.157µs 254 DEBUG[07-01|14:12:41.637] Ledger support enabled 255 DEBUG[07-01|14:12:41.637] Trezor support enabled via HID 256 DEBUG[07-01|14:12:41.638] Trezor support enabled via WebUSB 257 INFO [07-01|14:12:41.638] Audit logs configured file=audit.log 258 DEBUG[07-01|14:12:41.638] IPC registered namespace=account 259 INFO [07-01|14:12:41.638] IPC endpoint opened url=$HOME/.clef/clef.ipc 260 ------- Signer info ------- 261 * intapi_version : 7.0.0 262 * extapi_version : 6.0.0 263 * extapi_http : n/a 264 * extapi_ipc : $HOME/.clef/clef.ipc 265 ``` 266 267 Then test signing, once with `bazonk` and once without: 268 269 ``` 270 $ echo '{"id": 1, "jsonrpc":"2.0", "method":"account_signData", "params":["data/plain", "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", "0x202062617a6f6e6b2062617a2067617a0a"]}' | nc -U ~/.clef/clef.ipc 271 {"jsonrpc":"2.0","id":1,"result":"0x4f93e3457027f6be99b06b3392d0ebc60615ba448bb7544687ef1248dea4f5317f789002df783979c417d969836b6fda3710f5bffb296b4d51c8aaae6e2ac4831c"} 272 273 $ echo '{"id": 1, "jsonrpc":"2.0", "method":"account_signData", "params":["data/plain", "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", "0x2020626f6e6b2062617a2067617a0a"]}' | nc -U ~/.clef/clef.ipc 274 {"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"Request denied"}} 275 ``` 276 277 Meanwhile, in the Clef output log you can see: 278 279 ```text 280 INFO [02-21|14:42:41] Op approved 281 INFO [02-21|14:42:56] Op rejected 282 ``` 283 284 The signer also stores all traffic over the external API in a log file. The last 4 lines shows the two requests and their responses: 285 286 ```text 287 $ tail -n 4 audit.log 288 t=2019-07-01T15:52:14+0300 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"NA\",\"local\":\"NA\",\"scheme\":\"NA\",\"User-Agent\":\"\",\"Origin\":\"\"}" addr="0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 [chksum INVALID]" data=0x202062617a6f6e6b2062617a2067617a0a content-type=data/plain 289 t=2019-07-01T15:52:14+0300 lvl=info msg=SignData api=signer type=response data=4f93e3457027f6be99b06b3392d0ebc60615ba448bb7544687ef1248dea4f5317f789002df783979c417d969836b6fda3710f5bffb296b4d51c8aaae6e2ac4831c error=nil 290 t=2019-07-01T15:52:23+0300 lvl=info msg=SignData api=signer type=request metadata="{\"remote\":\"NA\",\"local\":\"NA\",\"scheme\":\"NA\",\"User-Agent\":\"\",\"Origin\":\"\"}" addr="0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3 [chksum INVALID]" data=0x2020626f6e6b2062617a2067617a0a content-type=data/plain 291 t=2019-07-01T15:52:23+0300 lvl=info msg=SignData api=signer type=response data= error="Request denied" 292 ``` 293 294 For more details on writing automatic rules, please see the [rules spec](https://github.com/tacshi/go-ethereum/blob/master/cmd/clef/rules.md). 295 296 ## Geth integration 297 298 Of course, as awesome as Clef is, it's not feasible to interact with it via JSON RPC by hand. Long term, we're hoping to convince the general Ethereum community to support Clef as a general signer (it's only 3-5 methods), thus allowing your favorite DApp, Metamask, MyCrypto, etc to request signatures directly. 299 300 Until then however, we're trying to pave the way via Geth. Geth v1.9.0 has built in support via `--signer <API endpoint>` for using a local or remote Clef instance as an account backend! 301 302 We can try this by running Clef with our previous rules on Rinkeby (for now it's a good idea to allow auto-listing accounts, since Geth likes to retrieve them once in a while). 303 304 ```text 305 $ clef --keystore ~/.ethereum/rinkeby/keystore --chainid 4 --rules rules.js 306 ``` 307 308 In a different window we can start Geth, list our accounts, even list our wallets to see where the accounts originate from: 309 310 ```text 311 $ geth --rinkeby --signer=~/.clef/clef.ipc console 312 313 > eth.accounts 314 ["0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", "0x086278a6c067775f71d6b2bb1856db6e28c30418"] 315 316 > personal.listWallets 317 [{ 318 accounts: [{ 319 address: "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3", 320 url: "extapi://$HOME/.clef/clef.ipc" 321 }, { 322 address: "0x086278a6c067775f71d6b2bb1856db6e28c30418", 323 url: "extapi://$HOME/.clef/clef.ipc" 324 }], 325 status: "ok [version=6.0.0]", 326 url: "extapi://$HOME/.clef/clef.ipc" 327 }] 328 329 > eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[0]}) 330 ``` 331 332 Lastly, when we requested a transaction to be sent, Clef prompted us in the original window to approve it: 333 334 ```text 335 --------- Transaction request------------- 336 to: 0xD9C9Cd5f6779558b6e0eD4e6Acf6b1947E7fA1F3 337 from: 0xD9C9Cd5f6779558b6e0eD4e6Acf6b1947E7fA1F3 [chksum ok] 338 value: 0 wei 339 gas: 0x5208 (21000) 340 gasprice: 1000000000 wei 341 nonce: 0x2366 (9062) 342 343 Request context: 344 NA -> NA -> NA 345 346 Additional HTTP header data, provided by the external caller: 347 User-Agent: 348 Origin: 349 ------------------------------------------- 350 Approve? [y/N]: 351 > y 352 ``` 353 354 :boom: 355 356 _Note, if you enable the external signer backend in Geth, all other account management is disabled. This is because long term we want to remove account management from Geth._