Article written by Alex Lazar and published on 9/21/2024
Cross-chain bridges are hard and complex. What if it wasn’t like that?
There are a few important aspects to a cross-chain bridge imho:
Monitoring is relatively easy. Geth is good, Go is fast. It’s not an unsolved problem.
Decentralizing or otherwise making sure a bridge node doesn’t rug pull everyone is harder. It’s not unsolved, there are things like libp2p, cryptographic proofs, and so on.
Imho, it seems overly complex and not chain native. I’m thinking there may be a simpler architecture.
A bridge deployer
creates a “multi-sig”-like smart contract on 1 source and 1 destination chain.
The deployer
assigns a whitelist of node operator addresses on each chain.
The deployer
loses all control over the smart contracts as soon as deployed.
Each node operator is supposed to host the mini-bridge Go client code. They have to configure each client with the correct chains, self-provided RPC urls, private keys, and smart contract addresses (env vars).
The nodes are independent of each other entirely. They never communicate with each other.
Say the user does a transaction to start a cross-chain message. He has to pay a fee on the source chain within that transaction.
The nodes watch the blockchain, only final blocks. If a node finds a started cross-chain message, it validates it internally (within itself).
If it’s valid and this is the first node to process the cross-chain message, it adds it to the destination chain contract as a valid message with its data.
If it’s valid and not the first node to process it, it does a transaction on the destination chain to signal that it finds it valid. There should be a mechanism to check if the destination chain data has the same message.
Regardless of whether it’s the first or not, if it’s a valid message, it also does a transaction on the source chain to signal that it found the proposal valid. This creates an A -> B -> A situation.
If not valid, there are a few things we can do and I’m not sure which one is best yet.
The benefit of 1 is that it’s simple & cheap. The benefit of 2 & 3 is that it’s easier for the user to detect if the message is invalid.
If enough X of Y nodes voted the message as valid on destination chain,
the user can execute()
the message.
If enough X of Y nodes voted the message as valid on source chain,
the nodes can get their rewards.
This can create a world where nodes maliciously vote messages as valid on the source chain to steal the rewards, a sybil attack. The hope is that having a big enough X of Y prevents this.
There could be a mechanism for the user to pay on the source chain
such that the node operators execute()
for him on destination chain.
This helps in case the user doesn’t have gas on the destination chain.
It’s problematic because what if the message/call is malicious.
If not enough X of Y nodes voted the message as valid, we can have a mechanism to allow and incentivize nodes or other users to prune/delete stale messages after some time / an amount of blocks.
We can also have a mechanism for retries maybe.
Eventually maybe we can also add a mechanism to vote on adding/removing nodes from the bridge. Nodes would self-select to run the bridge.
It seems to me like this architecture is simpler and cheaper, more chain native, and easier to understand and audit.
I can see a few counter-arguments:
But these are the case for pretty much all of Chainlink (OCR, CCIP, etc) and a bunch of other bridges as well.
The OCR feed for ETH / USD on Ethereum for example has ~31 nodes, as of writing this. Most of them have less. And they are handpicked by Chainlink. CCIP is highly similar to OCR in terms of validators.
DeBridge has 12 off-chain validators and requires 8 of them to sign.
Axelar has ~75 as of writing this, but it used to have ~50 in 2022 and it worked fine.
I don’t think those 2 counter-arguments are that strong, by themselves.
If you have opinions on this, I’d love to hear them!
X / Twitter: https://x.com/_alexlazar_ Telegram: https://t.me/mauricedesaxe Email: [email protected]