diff --git a/cmd/signer/README.md b/cmd/signer/README.md index 4def84e014..25db2c9ee4 100644 --- a/cmd/signer/README.md +++ b/cmd/signer/README.md @@ -50,6 +50,53 @@ The general flow for signing a transaction using e.g. geth is as follows: In this case, `geth` would be started with `--externalsigner=http://localhost:8550` and would relay requests to `eth.sendTransaction`. +## TODOs + +Some snags and todos + +* The signer should take a startup param "--no-change", for UI:s 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. + +* It would be nice if the signer 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. + +* It should be possible to configure the signer 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". Similarly, it should be possible to configure +the signer to auto-allow listing (certain) accounts, instead of asking every time. + +* Upon startup, the signer should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode), +invoking methods with the following info: + * Version info about the signer + * Address of API (http/ipc) + * This makes it posible for the UI to use the api for creating transactions + * List of known accounts + +* 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 + +* Geth relay + - Geth should be started in `geth --external_signer localhost:8550`. + +* Geth checksum + - Currently, the Geth API:s 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. + - The Geth api should switch to use the same type, and relay `to`-account verbatim to the external api. + +* Wallets / accounts. Add API methods for wallets. + +* Storage + * An encrypted key-value storage should be implemented + * See [rules.md](rules.md) for more info about this. + +* Another potential thing to introduce is pairing. + * To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API). + * Thus geth/mist/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests. + * This feature would make the addition of rules less dangerous. + ## Communication ### External API @@ -642,55 +689,3 @@ A UI should conform to the following rules. along with the UI. - - - -## TODOs - -Some snags and todos - -* The audit-log perhaps leave some things to be desired. I have not found a perfect way to save an audit log of events. - -* The signer should take a startup param "--no-change", for UI:s 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. - -* It would be nice if the signer 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. - -* It should be possible to configure the signer 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". Similarly, it should be possible to configure -the signer to auto-allow listing (certain) accounts, instead of asking every time. - -* Upon startup, the signer should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode), -invoking methods with the following info: - * Version info about the signer - * Address of API (http/ipc) - * This makes it posible for the UI to use the api for creating transactions - * List of known accounts - -* 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 - -* Geth relay - - Geth should be started in `geth --external_signer localhost:8550`. -* Geth checksum - - Currently, the Geth API:s 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. - - The Geth api should switch to use the same type, and relay `to`-account verbatim to the external api. - -* Wallets / accounts. Add API methods for wallets. - -* Rules. In the future, it should be possible to specify rules, e.g. "Allow sending up to 1 eth per day to contract Y". Two problems: - * There needs to be a very good structure around rules. Either a full language (lua/js) or a limited but flexible syntax based on e.g. json/yaml. Kind of like a firewall ruleset. - * This implies that the signer would remember passwords, which is very problematic. However, a good UI implementation will want these things, - and it would be better to implement it once in the signer, than having UI:s develop their own remember-password logic. - * See [rules.md](rules.md) for more info about this. - -* Another potential thing to introduce is pairing. - * To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API). - * Thus geth/mist/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests. - * This feature would make the addition of rules less dangerous. \ No newline at end of file diff --git a/cmd/signer/rules.md b/cmd/signer/rules.md index 1b761f1120..3062fc0403 100644 --- a/cmd/signer/rules.md +++ b/cmd/signer/rules.md @@ -105,3 +105,108 @@ This would leave it up to the user to ensure that the `path/to/masterseed` is ha imagine leveraging OS-level keychains where supported. The setup is however in general similar to how ssh-keys are stored in `.ssh/`. +# Implementation status + +This is now implemented (with ephemeral non-encrypted storage for now, so not yet enabled). + +## Example 1: ruleset for a rate-limited window + + +```javascript + + function big(str){ + if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)} + return new BigNumber(str) + } + + // Time window: 1 week + var window = 1000* 3600*24*7; + + // Limit : 1 ether + var limit = new BigNumber("1e18"); + + function isLimitOk(transaction){ + var value = big(transaction.value) + // Start of our window function + var windowstart = new Date().getTime() - window; + + var txs = []; + var stored = storage.Get('txs'); + + if(stored != ""){ + txs = JSON.parse(stored) + } + // First, remove all that have passed out of the time-window + var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); + console.log(txs, newtxs.length); + + // Secondly, aggregate the current sum + sum = new BigNumber(0) + + sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum); + console.log("ApproveTx > Sum so far", sum); + console.log("ApproveTx > Requested", value.toNumber()); + + // Would we exceed weekly limit ? + return sum.plus(value).lt(limit) + + } + function ApproveTx(jsonstr){ + var r = JSON.parse(jsonstr) + if (isLimitOk(r.transaction)){ + return "Approve" + } + return "Nope" + } + + /** + * OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter + * 'response_str' contains the return value that will be sent to the external caller. + * The return value from this method is ignore - the reason for having this callback is to allow the + * ruleset to keep track of approved transactions. + * + * When implementing rate-limited rules, this callback should be used. + * If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user + * then accepts the transaction, this method will be called. + * + * TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx. + */ + function OnApprovedTx(response_str){ + console.log("OnApprovedTx > called with data\n\t "+response_str) + var resp = JSON.parse(response_str) + var value = big(resp.tx.value) + var txs = [] + // Load stored transactions + var stored = storage.Get('txs'); + if(stored != ""){ + txs = JSON.parse(stored) + } + // Add this to the storage + txs.push({tstamp: new Date().getTime(), value: value}); + storage.Put("txs", JSON.stringify(txs)); + } + +``` + +## Example 2: allow destination + +```javascript + + function ApproveTx(jsonstr){ + r = JSON.parse(jsonstr) + if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"} + if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"} + // Otherwise goes to manual processing + } + +``` + +## Example 3: Allow listing + +```javascript + + function ApproveListing(){ + return "Approve" + } + +``` \ No newline at end of file