{"id":13652106,"url":"https://github.com/canontech/proxy-hot-wallet","last_synced_at":"2025-04-23T03:30:32.395Z","repository":{"id":54180242,"uuid":"300936624","full_name":"canontech/proxy-hot-wallet","owner":"canontech","description":"Demo for a safe and effective custodial hot wallet architecture using features innovated by Substrate FRAME pallets and featured in production chains such as Polkadot and Kusama.","archived":false,"fork":false,"pushed_at":"2021-04-30T03:51:52.000Z","size":829,"stargazers_count":17,"open_issues_count":2,"forks_count":6,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-20T00:01:44.194Z","etag":null,"topics":["multisig-address","polkadot","substrate","transaction","transfer-funds"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/canontech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-03T17:22:30.000Z","updated_at":"2024-11-06T13:37:18.000Z","dependencies_parsed_at":"2022-08-13T08:31:07.046Z","dependency_job_id":null,"html_url":"https://github.com/canontech/proxy-hot-wallet","commit_stats":null,"previous_names":["emostov/proxy-hot-wallet"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canontech%2Fproxy-hot-wallet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canontech%2Fproxy-hot-wallet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canontech%2Fproxy-hot-wallet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canontech%2Fproxy-hot-wallet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/canontech","download_url":"https://codeload.github.com/canontech/proxy-hot-wallet/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250365192,"owners_count":21418646,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["multisig-address","polkadot","substrate","transaction","transfer-funds"],"created_at":"2024-08-02T02:00:55.419Z","updated_at":"2025-04-23T03:30:31.905Z","avatar_url":"https://github.com/canontech.png","language":"TypeScript","funding_links":[],"categories":["Tools"],"sub_categories":[],"readme":"# Proxy hot wallet demo for Substrate (Polkadot \u0026 Kusama)\n\nDemo for a safe and effective custodial hot wallet architecture using features innovated by [Substrate](https://substrate.dev/) FRAME pallets and featured in production chains such [Polkadot](https://polkadot.network/) and [Kusama](https://kusama.network/).\n\nBy [@joepetrowski](https://github.com/joepetrowski) \u0026 [@emostov](https://github.com/emostov)\n\n**N.B.** This demo needs some updates to reflect best practices. Primarily, there should be NonTransfer proxies that have the ability to remove the Transfer proxies if a questionable proxy announcement is made. Please file an issue if would like clarification and/or see the updates reflected in the demo.\n\n## Disclaimer\n\nThis repo is for demonstration purposes only.\n\n## Table of contents\n\n- [Background](#background)\n- [Technologies](#technologies)\n- [Architecture](#architecture)\n- [Demo outline](#demo-outline)\n- [Run](#run)\n\n## Background\n\nWhen managing significant sums of funds on behalf of other entities, a major challenge is moving around funds without compromising the private key of the deposit addresses. In traditional block chains the private key must be \"hot\" (on a device exposed to the internet) in order to efficiently and programmatically move funds from the account (i.e. accounts that a user might deposit funds to). The moment this \"hot\" key is comprised the attacker has total control of funds.\n\nIn this repo we demonstrate an architecture pattern enabled by the [Substrate FRAME](https://substrate.dev/docs/en/knowledgebase/runtime/frame) [`proxy`](https://github.com/paritytech/substrate/tree/master/frame/proxy), [`multisig`](https://github.com/paritytech/substrate/tree/master/frame/multisig) and [`utility`](https://github.com/paritytech/substrate/tree/master/frame/utility#for-pseudonymal-dispatch) (see pseudonymal dispatch) pallets, that minimizes attack vectors associated with operating a hot wallet as a custodian.\n\nThe \"hot\" account is a multisig composite address that adds a proxy which announces transactions that can be executed after some delay. Pseudonymal accounts are derived from the multisig address and can be generated for every new deposit by a user to keep accounting clear. The proxy account can regularly transfer funds from the derivative accounts to a cold storage location(s). If the system detects a announcement by the proxy for a transfer to a non-certified address, then the multisig accounts can broadcast transactions to revoke the proxies privileges within the announcement period and prevent any of the proxies announced transactions from being executed.\n\n## Technologies\n\n- [Parity Polkadot node implementation](https://github.com/paritytech/polkadot#polkadot)\n- [@substrate/txwrapper: offline transaction construction lib for Substrate](https://github.com/paritytech/txwrapper)\n- [@substrate/api-sidecar: RESTful api microservice for Substrate nodes](https://github.com/paritytech/substrate-api-sidecar)\n- [@polkadot/util-crypto: Substrate cryptography utility lib](https://github.com/polkadot-js/common/tree/master/packages/util-crypto)\n\n## Architecture\n\n![architecture](/src/static/architecture.png)\n\n### Setup\n\n1) Create m-of-n multisig MS from K = {k} keys, held by trusted parties (e.g. founders).\n2) Set a proxy H with time delay T for MS.\n3) Create derivative addresses D = {d} for user deposits.\n4) Create cold storage S (not discussed here).\n\n#### Simple use\n\n1) Proxy H announces call hash on-chain.\n2) H sends actual call to backend system.\n3) Backend ensures that call hash matches and parses call.\n4) Applies internal rule (e.g. to whitelisted address).\n5) If alerted, m-of-n K can reject the transaction.\n6) If timeout, H broadcasts the actual call.\n\n#### Normal transaction flow\n\n1) User i sends a deposit of v tokens to their addresses, di\n2) Listener observes balances.Transfer event to address di \n3) A machine** constructs the following call C***:\n\n      ```c\n      C = utility.as_derivative(\n              index: i,\n              call: balances.transfer(\n                  dest: S_pub,\n                  value: v,\n              )\n          )\n      ```\n\n4) Key H signs and broadcasts the transaction:\n\n    ```c\n    proxy.announce(real: MS_pub, call_hash: hash(C))\n    ```\n\n5) Listener observes announce transaction on-chain and asks for the call C. It verifies two things:\n\n    1) Its hash matches the announcement, and\n    2) Any internal rules, e.g. the transfer is to a whitelisted S.\n\n6) Verifications pass, no alarm.\n7) After time delay T, any account can broadcast the transaction:\n\n      ```c\n      proxy.proxy_announced(\n          delegate: H_pub,\n          real: MS_pub,\n          force_proxy_type: Any,\n          call: C,\n      )\n      ```\n\n8) Listener verifies that the transfer was successful when it sees a `balances.Transfer` event to address MS.\n\n** Can be any machine, even without access to key H.\n*** Note that it can transfer the full value v as the address H will pay the transaction fees.\n\n#### Call rejection\n\n1) Verification does not pass.\n2) Owners of K are alerted and have time T to react.\n3) Construct the following call R**:\n\n    ```c\n    R = proxy.reject_announcement(\n            delegate: H_pub,\n            call_hash: hash(C),\n        )\n    ```\n\n4) Owners of K broadcast the call R.\n5) Fallback: owners cannot either override the flag or broadcast their multisig transactions in time.\n6) System hold immortal transaction ready to remove H as a proxy for MS (`proxy.remove_proxies()`). The system can submit this at any point. Note: only the first multisig transaction can be immortal and stored; the remaining need the `TimePoint` of the first multisig transaction so they need to be constructed in real time.\n\n** In the event of a malicious announcement it is always recommended to call `proxy.remove_proxies()` ASAP.\n\n#### Misc. optimizations\n\n1) Use batch transactions, e.g.:\n\n    ```c\n    utility.batch(\n      utility.as_derivative(index: 0, call: C0),\n      utility.as_derivative(index: 1, call: C1),\n      utility.as_derivative(index: 2, call: C2),\n      …\n    )\n    ```\n\n2) Reduce T and only use the fallback for faster settlement.\n3) Use anonymous proxies for MS to allow member change, in case some K\u003csub\u003ei\u003c/sub\u003e is compromised.\n\n## Demo Outline\n\n1) Generate 6 keyrings and make sure accounts have funds\n    - 3 for multisig\n    - 1 for proxy\n    - 1 for stash\n    - 1 for depositor\n2) Generate a 2-of-3 multisig address and log it to console\n3) Transfer funds from Alice to the multisig\n    - `balances.transfer()`\n4) Set the 4th key as a proxy for the multisig\n    - `Call = proxy.addProxy(key4, Any, 10)` // 10 blocks = 1 min\n    - `multisig.approve_as_multi(hash(Call))`\n    - `multisig.as_multi(Call)`\n5) Create derivative accounts from multisig address for depositor(s) to send tokens to\n6) Transfer funds `v` from depositor to two derivative accounts\n    - `balances.transfer()`\n7) Create two calls: one good and one bad\n    - `C0 = utility.as_derivative(0, balances.transfer(stash, v))`\n    - `C1 = utility.as_derivative(1, balances.transfer(attacker, v))`\n8) Demonstrate the happy path\n    - `key4` broadcasts `proxy.announce(hash(C0))` and sends `C0` to another worker\n    - safety worker decodes `C0` and ensures that destination address is `stash`\n    - after 10 blocks, `key4` broadcasts `proxy.proxy_announced(C0)`\n9) Demonstrate adversarial path\n    - `key4` broadcasts `proxy.announce(hash(C1))` and sends `C1` to safety worker\n    - safety worker decodes `C1` and sees that destination address is not `stash`\n    - `multisig.approve_as_multi(proxy.remove_proxies(hash(C1)))`\n    - `multisig.as_multi(proxy.reject_announcement(hash(C1)))`\n\n## Run\n\n1) This demo relies on using a parity polkadot development node; you can download the [source here](https://github.com/paritytech/polkadot). Follow the instructions to download and compile the code.\n2) Make sure the node's database is empty by running: `./target/release/polkadot purge-chain --dev` (**N.B.** the nodes DB must be purged before every run of the demo script)\n3) Start the node by running `./target/release/polkadot --dev`\n4) In another terminal session change directories to this project and install dependencies by running `yarn`\n5) Start up Sidecar by running `yarn sidecar`\n6) In another terminal session, make sure you are in this project directory and start the demo by running `yarn start`\n\nNote: this script assumes the polkadot node and Sidecar are using the defualt development ports.\n\n[Sample demo output](/out.log)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanontech%2Fproxy-hot-wallet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcanontech%2Fproxy-hot-wallet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanontech%2Fproxy-hot-wallet/lists"}