General design
Threshold signature producer (hereinafter TSS or party or TSS party) is a decentralized service that performs
verification of the actions happening on the source chains and, in couple with other TSS parties, generates a signed
transaction to perform corresponding action on the target chain. Using the trustless threshold ECDSA protocol, TSS
parties can securely generate private shares from common public keys without involving any centralized participant. The
usage of the ECDSA signature over the secp256k1 elliptic curve allows TSS to cover the most popular blockchains,
including Bitcoin, BitcoinCash, and Ethereum (EVM-compatible chains).
Interaction with different chain types requires separate solutions for them. Since the selected threshold signature library (Binance tss-lib also supports the threshold EdDSA, it becomes possible to extend the existing logic to all blockchains that support ECDSA or EdDSA on the native or smart-contract layer.
We aim to support the following chains:
- Bitcoin;
- EVM;
- Zano.
On the first iteration we introduce the centralized version of the signer service, it is assumed to be used for the Bridgeless launch, to enable the integration and testing of some other Bridgeless components.
Centralized signer
- Repository: bridgeless-signer
Bridgeless signer is a centralized service that performs verification of the bridge deposit actions happening on the source chains and submit a signed transaction to perform corresponding withdraw action on the target chain.
Although the service is built using GRPC and Protocol Buffers, it provides a REST gateway to submit deposit transactions and check the status of the according withdrawal.
Design
Service Core logic (/internal/core) consists of two parts: JSON API to submit deposits and check the
status of the according withdrawal (/core/api), and different handlers to process withdrawals part by part,
where each part is being sent/consumed to/from RabbitMQ queues of messages (/core/rabbitmq).
There are two types of message consuming implemented here:
- Default consuming (
base) - used to process incoming message immediately after consuming. Is used for default scenarios where request can be processed independently; - Batch consuming (
batch) - used to collect incoming messages and process them after some period. Is used for requests that should be better grouped and processed together to optimize/enhance processing (f. e. Bitcoin transactions batching, Core tx submitting batching).
Multiple request handlers, that implement interface required by either base or
batch consumer, handle specific part of the withdrawal process (/rabbitmq/consumer/processors). They use
bridge processor (/internal/bridge/processor) to handle the request and then route the next one using
RabbitMQ request producer (/rabbitmq/producer).
In order to avoid unexpected errors each request to process withdrawal can be resent up to
maxCount times in case of some system or third party services failure.
Bridge processor is the core system that interacts with database, Bridge Core module and chains for data
retrieval/parsing/sending etc. It contains a set of proxies - implementations of the generalized method
to work with different chains (/bridge/proxy). For now it supports EVM-based chains (/proxy/evm) and
Bitcoin (/proxy/btc).
To better understand how withdrawals are processed by the service, lets take a look on the flow of requests processing:
- Base/Batch Consumer reads pending request from the RabbitMQ queue;
- Request is processed by specific consumer implementation;
- Consumer implementation uses Bridge processor to handle request;
- Bridge processor uses proxy to extract/parse/transform/send specific chain data;
- After Bridge processor processed request, Consumer implementation forms and sends request to process the next part of the withdrawal using Producer. Unexpectedly failed requests can also be resent by Base/Batch Consumer;
- Returning to step #1 unless all parts of the withdrawal process are successfully finished.
API
Swagger documentation: api.testnet.bridgeless.com/api
-
CheckWithdrawal GET /check/$originTxId
Request information about the withdrawal. The $originTxId parameter consists of three parts: $hash-$eventID-$chainID. For Bitcoin the eventID is always 0. For Zano eventID depends on the service_entries position of the deposit information.For EVM chains event id is an index of corresponding Deposit log in the transaction.
Response sample:
{
"status": "PROCESSING",
"depositTransaction": {
"hash": "string",
"chainId": "string"
},
"depositData": {
"eventIndex": "string",
"blockNumber": "string",
"depositor": "string",
"depositAmount": "string",
"depositToken": "string",
"receiver": "string",
"withdrawalToken": "string",
"isWrapped": true,
"withdrawalAmount": "string",
"signature": "string"
},
"withdrawalTransaction": {
"hash": "string",
"chainId": "string"
},
"submitStatus": "NOT_SUBMITTED"
} -
SubmitWithdrawal POST /submit
Submit info about deposit to initiate a withdrawal flow. If the withdrawal fails, this method allows to re-initiate flow. For the Zano and Bitcoin chains the withdrawal will be done automatically, while for the EVM chains user may need to fetch a signature using the CheckWithdrawal or from the bridge module on core.
Request sample:
{
"deposit": {
"txHash": "string",
"txEventIndex": "string",
"chainId": "string"
}
} -
Websocket CheckWithdrawal /ws/check/$originTxId
-
Initialization: same request as in CheckWithdrawal;
-
Channel information: same as in CheckWithdrawal for each status change.
Connection will be automatically closed for the following statuses:
- TX_PENDING
- TX_SUCCESSFUL
- TX_FAILED
- WITHDRAWAL_SIGNED
- INVALID
- FAILED
-