|
| 1 | +# RGB Multisig Bridge |
| 2 | + |
| 3 | +RGB multisig bridge to enable collaboration between a group of cosigners. |
| 4 | + |
| 5 | +Cosigners are the members of the multisig setup, they propose new operations |
| 6 | +and review, then accept or refuse, the ones proposed by other cosigners. |
| 7 | + |
| 8 | +Optionally, third parties can be given watch-only access. |
| 9 | + |
| 10 | +Each cosigner runs an [rgb-lib] wallet with the same multisig setup, used for |
| 11 | +all operations except signing, and a singlesig (software or hardware) wallet |
| 12 | +only used for signing. |
| 13 | +Watch-only parties only run the multisig wallet and they're only allowed access |
| 14 | +to a subset of the exposed APIs. |
| 15 | + |
| 16 | +## Overview |
| 17 | + |
| 18 | +The bridge allows a group of cosigners to exchange the information needed to |
| 19 | +cooperate a multisig setup. |
| 20 | + |
| 21 | +On initial setup, authentication and the cosigners are configured. |
| 22 | +Authentication is handled via biscuit tokens, see [Authentication] for |
| 23 | +details. Configuration includes the xPubs for all cosigners, the thresholds for |
| 24 | +operation approval and rgb-lib versioning, see [Configuration] for details. |
| 25 | + |
| 26 | +Once the bridge is operational, cosigners can use it to propose a new |
| 27 | +operation, which is then retrieved by the others, who will review it and |
| 28 | +respond to either approve or deny it. |
| 29 | + |
| 30 | +There can only be 1 pending operation at a time. Once enough cosigners have |
| 31 | +responded to either reach the threshold (operation approved) or make it |
| 32 | +impossible to reach (operation discarded), the operation moves to its final |
| 33 | +state and cosigners can process (approved) or skip (discarded) the operation. |
| 34 | + |
| 35 | +Cosigners get the operations from the bridge by their (progressive) ID and are |
| 36 | +responsible for keeping track of the last operation they have processed. When a |
| 37 | +new operation is retrieved from the bridge, it can either be pending (to be |
| 38 | +reviewed and responded to), approved (to be processed) or discarded (to be |
| 39 | +skipped). |
| 40 | + |
| 41 | +Operations must be processed in order. Cosigners are responsible to make sure |
| 42 | +they have processed all operations before they propose or process a new one. |
| 43 | +The bridge keeps track of the last processed operation for each cosigner in |
| 44 | +order to help prevent accidental out-of-order processing. |
| 45 | + |
| 46 | +It is advised not to run more than one copy of each cosigner, although the |
| 47 | +bridge design should allow such mode of operation. |
| 48 | + |
| 49 | +## Requirements |
| 50 | + |
| 51 | +The [biscuit-cli] tool is needed to setup and manage authentication. |
| 52 | + |
| 53 | +Local installation requires [cargo]. |
| 54 | + |
| 55 | +Each cosigner must run a compatible version of rgb-lib, specifically: |
| 56 | +- the configured rgb-lib version must be in the bridge's supported version range |
| 57 | +- all cosigners must use the rgb-lib version specified in the configuration file |
| 58 | + |
| 59 | +See [Configuration] for details on how the version boundaries are set. |
| 60 | + |
| 61 | +The service has currently only been tested on Linux but it may run on other |
| 62 | +operating systems as well. |
| 63 | + |
| 64 | +## Install |
| 65 | + |
| 66 | +Clone the project: |
| 67 | +```sh |
| 68 | +git clone https://github.com/RGB-Tools/rgb-multisig-bridge |
| 69 | +``` |
| 70 | + |
| 71 | +### Local |
| 72 | + |
| 73 | +To install the bridge locally, from the project root, run: |
| 74 | +```sh |
| 75 | +cargo install --locked --path . |
| 76 | +``` |
| 77 | + |
| 78 | +This will produce the `rgb-multisig-bridge` binary. |
| 79 | + |
| 80 | +### Docker |
| 81 | + |
| 82 | +To build the docker image, run: |
| 83 | +```sh |
| 84 | +docker build -t rgb-multisig-bridge . |
| 85 | +``` |
| 86 | + |
| 87 | +## Setup |
| 88 | + |
| 89 | +Before the bridge can be run, authentication (root keys and tokens) needs to be |
| 90 | +setup and the service needs to be configured. |
| 91 | + |
| 92 | +### Authentication |
| 93 | + |
| 94 | +Authentication is handled via [Biscuit tokens]. |
| 95 | + |
| 96 | +To setup the authentication, a root key pair needs to be generated. The private |
| 97 | +key is used to generate new signed tokens manually. The public key is |
| 98 | +configured in the bridge and is used to verify the tokens provided by users in |
| 99 | +request headers. |
| 100 | + |
| 101 | +Authentication is mandatory and cannot be disabled. |
| 102 | + |
| 103 | +#### Root key pair |
| 104 | + |
| 105 | +Key and token generation are handled via the biscuit CLI, which can be |
| 106 | +installed via cargo: |
| 107 | +```sh |
| 108 | +cargo install biscuit-cli |
| 109 | +``` |
| 110 | +or a pre-built binary can be downloaded from the [biscuit-cli releases page]. |
| 111 | + |
| 112 | +To generate the private key, run: |
| 113 | +```sh |
| 114 | +biscuit keypair --only-private-key > private-key-file |
| 115 | +``` |
| 116 | + |
| 117 | +To generate the corresponding public key, run: |
| 118 | +```sh |
| 119 | +biscuit keypair --from-file private-key-file --only-public-key |
| 120 | +``` |
| 121 | +See [Configuration] for how to configure this in the bridge. |
| 122 | + |
| 123 | +Notes: |
| 124 | +- the root private key must be kept secret and it is advised to store it safely |
| 125 | + in a password manager |
| 126 | +- if the root private key is compromised or lost it needs to be abandoned and |
| 127 | + a new one needs to be generated, along with its public counterpart |
| 128 | +- changing root key pair means updating the configured root public key, which |
| 129 | + will make all previous tokens become invalid so new ones will need to be |
| 130 | + generated and distributed |
| 131 | + |
| 132 | +#### Tokens |
| 133 | + |
| 134 | +Tokens must have a role, either `cosigner` or `watch-only`. Cosigner tokens |
| 135 | +must embed their xPub, watch-only tokens must not embed an xPub. Cosigner |
| 136 | +tokens grant access to all APIs, watch-only tokens only grant access to a |
| 137 | +subset of the APIs. |
| 138 | + |
| 139 | +To generate a cosigner token, run: |
| 140 | +```sh |
| 141 | +echo 'role("cosigner"); xpub("<cosigner_xpub>");' \ |
| 142 | + | biscuit generate --private-key-file private-key-file - |
| 143 | +``` |
| 144 | +Repeat this for all cosigners, each identified by its xPub. |
| 145 | + |
| 146 | +To generate a watch-only token, run: |
| 147 | +```sh |
| 148 | +echo 'role("watch-only");' \ |
| 149 | + | biscuit generate --private-key-file private-key-file - |
| 150 | +``` |
| 151 | + |
| 152 | +Tokens can also carry an **expiry** date. A `check` clause can be added to |
| 153 | +enforce it. Here's an example for a watch-only token: |
| 154 | +```sh |
| 155 | +echo 'role("watch-only"); check if time($t), $t <= 2026-12-25T00:00:00Z;' \ |
| 156 | + | biscuit generate --private-key-file private-key-file - |
| 157 | +``` |
| 158 | + |
| 159 | +Tokens can be revoked, but this features is not implemented at the moment. |
| 160 | + |
| 161 | +### Configuration |
| 162 | + |
| 163 | +The service needs a data directory and a TOML configuration file. |
| 164 | + |
| 165 | +The storage data directory (e.g. `data`) is passed as a CLI parameter when |
| 166 | +starting the service. The configuration file is named `config.toml` and is |
| 167 | +located inside the data directory (e.g. `data/config.toml`). |
| 168 | + |
| 169 | +The configuration file requires the following parameters to be set: |
| 170 | +- `cosigner_xpubs`: list of the cosigner xPubs |
| 171 | +- `threshold_colored`: the threshold for colored operations |
| 172 | +- `threshold_vanilla`: the threshold for vanilla operations |
| 173 | +- `root_public_key`: the 32-byte hex-encoded authentication root public key |
| 174 | + (without the `ed25519/` prefix) |
| 175 | +- `rgb_lib_version`: the `<major.minor>` rgb-lib version that all cosigners |
| 176 | + must use |
| 177 | + |
| 178 | +Notes: |
| 179 | +- after the service has started, the `cosigner_xpubs` and `threshold_*` |
| 180 | + parameters cannot be changed |
| 181 | +- `rgb_lib_version` must fall in the `MIN_RGB_LIB_VERSION`-`MAX_RGB_LIB_VERSION` |
| 182 | + range, defined in `src/startup.rs` |
| 183 | + |
| 184 | +An example configuration file: |
| 185 | +```toml |
| 186 | +cosigner_xpubs = [ |
| 187 | + "tpubD6NzVbkrYhZ4XJ6aDsDYTCUkn1QqC6ie7eappEWB823FLSsRo1VBoEmtQVPJEJYdBt1UArW74BJg54FbW217Xoae6SDgj71JQZTfYCSJUyy", |
| 188 | + "tpubD6NzVbkrYhZ4XoJ4SGokACCMyKUYycuuu4tNDAW9qQrksXPNU9C9jeqQJQsdd18Dgt5v2hcc1w4qjNqYQg4nJ15YQNBHsWUuv2cEmneU7Mn", |
| 189 | + "tpubD6NzVbkrYhZ4WYbMkJwEwwTsQfjND3xNcXF6MoG7Ge8DbP8yWAkeg7DKPcuYfuHZYxCGWg9bFsAKLvJjb66LRM1wAkeszXKNAZdPpwnfHtd", |
| 190 | + "tpubD6NzVbkrYhZ4Yj7WVQNN28FDdpGyyscw1vi73xuxNoKqQ6uStVh3Pp11sh6y1PT7ohULyP6suzZkDBUuLvx7qd3YK4eU36rxAL9wdKRnVJk", |
| 191 | +] |
| 192 | +threshold_colored = 3 |
| 193 | +threshold_vanilla = 2 |
| 194 | +root_public_key = "df200ea3dab3eae6e518e55e6853dc39c50979d77a7d3d36c964c534c66bfad2" |
| 195 | +rgb_lib_version = "0.3" |
| 196 | +``` |
| 197 | + |
| 198 | +## Run |
| 199 | + |
| 200 | +Once the installation and initial setup are complete, the bridge daemon can be |
| 201 | +started. |
| 202 | + |
| 203 | +### Local |
| 204 | + |
| 205 | +To start the bridge daemon locally, run: |
| 206 | +```sh |
| 207 | +rgb-multisig-bridge <data_dir> |
| 208 | +``` |
| 209 | + |
| 210 | +The data directory needs to exist and contain the configuration file. |
| 211 | + |
| 212 | +### Docker |
| 213 | + |
| 214 | +To start the bridge container, run: |
| 215 | +```sh |
| 216 | +docker run -it \ |
| 217 | + -p 3001:3001 \ |
| 218 | + -v <host_dir_or_volume>:/srv/data \ |
| 219 | + rgb-multisig-bridge |
| 220 | +``` |
| 221 | + |
| 222 | +Notes: |
| 223 | +- if using a host directory, it doesn't need to already exist |
| 224 | +- it is advised not to use the same host data directory for both local and |
| 225 | + docker running, as the docker container runs as root and may change file |
| 226 | + permissions |
| 227 | + |
| 228 | +## Stop |
| 229 | + |
| 230 | +To stop the daemon press `Ctrl+C` on the console where it is running or, if |
| 231 | +running in docker, stop the container. |
| 232 | + |
| 233 | +## Use |
| 234 | + |
| 235 | +Once the daemon is running, it can be operated via HTTP JSON APIs. |
| 236 | + |
| 237 | +The node currently exposes the following APIs: |
| 238 | +- `/bumpaddressindices` (POST) |
| 239 | +- `/getcurrentaddressindices` (GET) |
| 240 | +- `/getfile` (POST) |
| 241 | +- `/getlastprocessedopidx` (GET) |
| 242 | +- `/getoperationbyidx` (POST) |
| 243 | +- `/info` (GET) |
| 244 | +- `/markoperationprocessed` (POST) |
| 245 | +- `/postoperation` (POST) |
| 246 | +- `/respondtooperation` (POST) |
| 247 | + |
| 248 | +See the [OpenAPI specification] for details. |
| 249 | + |
| 250 | +All requests must include the Biscuit token in the `Authorization` header. |
| 251 | + |
| 252 | +### Swagger |
| 253 | + |
| 254 | +A Swagger UI for the `master` branch is generated from the specification and |
| 255 | +made available at https://rgb-tools.github.io/rgb-multisig-bridge. |
| 256 | + |
| 257 | +A local copy can be exposed. To do so, from the project root, run: |
| 258 | +```sh |
| 259 | +docker run -it \ |
| 260 | + -p 8246:8080 \ |
| 261 | + -e SWAGGER_JSON=/var/specs/openapi.yaml \ |
| 262 | + -v $PWD/openapi.yaml:/var/specs/openapi.yaml \ |
| 263 | + swaggerapi/swagger-ui |
| 264 | +``` |
| 265 | +It can then be accessed by pointing a browser at `http://localhost:8246`. |
| 266 | + |
| 267 | +If a daemon is running on the local host on the default port (3001), the APIs |
| 268 | +can be called directly from the Swagger UI. |
| 269 | + |
| 270 | +Authentication is achieved by adding the token via the Authorize button (lock |
| 271 | +icon) at the top right, pasting the token and clicking `Authorize`. |
| 272 | + |
| 273 | +### Curl |
| 274 | + |
| 275 | +APIs can be called via curl. |
| 276 | + |
| 277 | +An example `getoperationbyidx` call: |
| 278 | +```sh |
| 279 | +curl -X POST -H "Content-type: application/json" \ |
| 280 | + -H "Authorization: Bearer <token>" \ |
| 281 | + -d '{"operation_idx": "1"}' \ |
| 282 | + http://localhost:3001/getoperationbyidx |
| 283 | +``` |
| 284 | + |
| 285 | + |
| 286 | +[Authentication]: #authentication |
| 287 | +[Biscuit tokens]: https://www.biscuitsec.org/ |
| 288 | +[Configuration]: #configuration |
| 289 | +[OpenAPI specification]: /openapi.yaml |
| 290 | +[biscuit-cli releases page]: https://github.com/eclipse-biscuit/biscuit-cli/releases |
| 291 | +[biscuit-cli]: https://github.com/eclipse-biscuit/biscuit-cli |
| 292 | +[cargo]: https://github.com/rust-lang/cargo |
| 293 | +[rgb-lib]: https://github.com/RGB-Tools/rgb-lib |
0 commit comments