RGB multisig bridge to enable collaboration between a group of cosigners.
Cosigners are the members of the multisig setup, they propose new operations and review, then accept or refuse, the ones proposed by other cosigners.
Optionally, third parties can be given watch-only access.
Each cosigner runs an rgb-lib wallet with the same multisig setup, used for all operations except signing, and a singlesig (software or hardware) wallet only used for signing. Watch-only parties only run the multisig wallet and they're only allowed access to a subset of the exposed APIs.
The bridge allows a group of cosigners to exchange the information needed to cooperate a multisig setup.
On initial setup, authentication and the cosigners are configured. Authentication is handled via biscuit tokens, see Authentication for details. Configuration includes the xPubs for all cosigners, the thresholds for operation approval and rgb-lib versioning, see Configuration for details.
Once the bridge is operational, cosigners can use it to propose a new operation, which is then retrieved by the others, who will review it and respond to either approve or deny it.
There can only be 1 pending operation at a time. Once enough cosigners have responded to either reach the threshold (operation approved) or make it impossible to reach (operation discarded), the operation moves to its final state and cosigners can process (approved) or skip (discarded) the operation.
Cosigners get the operations from the bridge by their (progressive) ID and are responsible for keeping track of the last operation they have processed. When a new operation is retrieved from the bridge, it can either be pending (to be reviewed and responded to), approved (to be processed) or discarded (to be skipped).
Operations must be processed in order. Cosigners are responsible to make sure they have processed all operations before they propose or process a new one. The bridge keeps track of the last processed operation for each cosigner in order to help prevent accidental out-of-order processing.
It is advised not to run more than one copy of each cosigner, although the bridge design should allow such mode of operation.
The biscuit-cli tool is needed to setup and manage authentication.
Local installation requires cargo.
Each cosigner must run a compatible version of rgb-lib, specifically:
- the configured rgb-lib version must be in the bridge's supported version range
- all cosigners must use the rgb-lib version specified in the configuration file
See Configuration for details on how the version boundaries are set.
The service has currently only been tested on Linux but it may run on other operating systems as well.
Clone the project:
git clone https://github.com/RGB-Tools/rgb-multisig-bridgeTo install the bridge locally, from the project root, run:
cargo install --locked --path .This will produce the rgb-multisig-bridge binary.
To build the docker image, run:
docker build -t rgb-multisig-bridge .Before the bridge can be run, authentication (root keys and tokens) needs to be setup and the service needs to be configured.
Authentication is handled via Biscuit tokens.
To setup the authentication, a root key pair needs to be generated. The private key is used to generate new signed tokens manually. The public key is configured in the bridge and is used to verify the tokens provided by users in request headers.
Authentication is mandatory and cannot be disabled.
Key and token generation are handled via the biscuit CLI, which can be installed via cargo:
cargo install biscuit-clior a pre-built binary can be downloaded from the biscuit-cli releases page.
To generate the private key, run:
biscuit keypair --only-private-key > private-key-fileTo generate the corresponding public key, run:
biscuit keypair --from-file private-key-file --only-public-keySee Configuration for how to configure this in the bridge.
Notes:
- the root private key must be kept secret and it is advised to store it safely in a password manager
- if the root private key is compromised or lost it needs to be abandoned and a new one needs to be generated, along with its public counterpart
- changing root key pair means updating the configured root public key, which will make all previous tokens become invalid so new ones will need to be generated and distributed
Tokens must have a role, either cosigner or watch-only. Cosigner tokens
must embed their xPub, watch-only tokens must not embed an xPub. Cosigner
tokens grant access to all APIs, watch-only tokens only grant access to a
subset of the APIs.
To generate a cosigner token, run:
echo 'role("cosigner"); xpub("<cosigner_xpub>");' \
| biscuit generate --private-key-file private-key-file -Repeat this for all cosigners, each identified by its xPub.
To generate a watch-only token, run:
echo 'role("watch-only");' \
| biscuit generate --private-key-file private-key-file -Tokens can also carry an expiry date. A check clause can be added to
enforce it. Here's an example for a watch-only token:
echo 'role("watch-only"); check if time($t), $t <= 2026-12-25T00:00:00Z;' \
| biscuit generate --private-key-file private-key-file -Tokens can be revoked, but this features is not implemented at the moment.
The service needs a data directory and a TOML configuration file.
The storage data directory (e.g. data) is passed as a CLI parameter when
starting the service. The configuration file is named config.toml and is
located inside the data directory (e.g. data/config.toml).
The configuration file requires the following parameters to be set:
cosigner_xpubs: list of the cosigner xPubsthreshold_colored: the threshold for colored operationsthreshold_vanilla: the threshold for vanilla operationsroot_public_key: the 32-byte hex-encoded authentication root public key (without theed25519/prefix)rgb_lib_version: the<major.minor>rgb-lib version that all cosigners must use
Notes:
- after the service has started, the
cosigner_xpubsandthreshold_*parameters cannot be changed rgb_lib_versionmust fall in theMIN_RGB_LIB_VERSION-MAX_RGB_LIB_VERSIONrange, defined insrc/startup.rs
An example configuration file:
cosigner_xpubs = [
"tpubD6NzVbkrYhZ4XJ6aDsDYTCUkn1QqC6ie7eappEWB823FLSsRo1VBoEmtQVPJEJYdBt1UArW74BJg54FbW217Xoae6SDgj71JQZTfYCSJUyy",
"tpubD6NzVbkrYhZ4XoJ4SGokACCMyKUYycuuu4tNDAW9qQrksXPNU9C9jeqQJQsdd18Dgt5v2hcc1w4qjNqYQg4nJ15YQNBHsWUuv2cEmneU7Mn",
"tpubD6NzVbkrYhZ4WYbMkJwEwwTsQfjND3xNcXF6MoG7Ge8DbP8yWAkeg7DKPcuYfuHZYxCGWg9bFsAKLvJjb66LRM1wAkeszXKNAZdPpwnfHtd",
"tpubD6NzVbkrYhZ4Yj7WVQNN28FDdpGyyscw1vi73xuxNoKqQ6uStVh3Pp11sh6y1PT7ohULyP6suzZkDBUuLvx7qd3YK4eU36rxAL9wdKRnVJk",
]
threshold_colored = 3
threshold_vanilla = 2
root_public_key = "df200ea3dab3eae6e518e55e6853dc39c50979d77a7d3d36c964c534c66bfad2"
rgb_lib_version = "0.3"Once the installation and initial setup are complete, the bridge daemon can be started.
To start the bridge daemon locally, run:
rgb-multisig-bridge <data_dir>The data directory needs to exist and contain the configuration file.
To start the bridge container, run:
docker run -it \
-p 3001:3001 \
-v <host_dir_or_volume>:/srv/data \
rgb-multisig-bridgeNotes:
- if using a host directory, it doesn't need to already exist
- it is advised not to use the same host data directory for both local and docker running, as the docker container runs as root and may change file permissions
To stop the daemon press Ctrl+C on the console where it is running or, if
running in docker, stop the container.
Once the daemon is running, it can be operated via HTTP JSON APIs.
The node currently exposes the following APIs:
/bumpaddressindices(POST)/getcurrentaddressindices(GET)/getfile(POST)/getlastprocessedopidx(GET)/getoperationbyidx(POST)/info(GET)/markoperationprocessed(POST)/postoperation(POST)/respondtooperation(POST)
See the OpenAPI specification for details.
All requests must include the Biscuit token in the Authorization header.
A Swagger UI for the master branch is generated from the specification and
made available at https://rgb-tools.github.io/rgb-multisig-bridge.
A local copy can be exposed. To do so, from the project root, run:
docker run -it \
-p 8246:8080 \
-e SWAGGER_JSON=/var/specs/openapi.yaml \
-v $PWD/openapi.yaml:/var/specs/openapi.yaml \
swaggerapi/swagger-uiIt can then be accessed by pointing a browser at http://localhost:8246.
If a daemon is running on the local host on the default port (3001), the APIs can be called directly from the Swagger UI.
Authentication is achieved by adding the token via the Authorize button (lock
icon) at the top right, pasting the token and clicking Authorize.
APIs can be called via curl.
An example getoperationbyidx call:
curl -X POST -H "Content-type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{"operation_idx": "1"}' \
http://localhost:3001/getoperationbyidx