Title: Cross-Chain Bridge Description: Enabling transfer of value between sidechains Type: Draft Author: Mayukha Vadari (Ripple) Scott Determan (Ripple)
core_protocol_changes_required: true
A bridge connects two blockchains: a locking chain and an issuing chain (also called a mainchain and a sidechain). Both are independent ledgers, with their own validators and potentially their own custom transactions. Importantly, there is a way to move assets from the locking chain to the issuing chain and a way to return those assets from the issuing chain back to the locking chain: the bridge. This key operation is called a cross-chain transfer. In this proposal, a cross-chain transfer is not a single transaction. It happens on two chains, requires multiple transactions, and involves an additional server type called a "witness".
A bridge does not exchange assets between two ledgers. Instead, it locks assets on one ledger (the "locking chain") and represents those assets with wrapped assets on another chain (the "issuing chain"). A good model to keep in mind is a box with an infinite supply of wrapped assets. Putting an asset from the locking chain into the box will release a wrapped asset onto the issuing chain. Putting a wrapped asset from the issuing chain back into the box will release one of the existing locking chain assets back onto the locking chain. There is no other way to get assets into or out of the box. Note that there is no way for the box to "run out of" wrapped assets - it has an infinite supply.
┌─┐┌─┐┌─┐┌─┐┌─┐
└─┘└─┘└─┘└─┘└─┘
Witnesses
┌─────────────────────────┐ ┌────────────────────────────┐
│ │ │ │
│ Locking Chain │ │ Issuing Chain │
│ │ │ │
│ Lock XRP |--------------------->│ Issue wXRP │
│ │ │ │
│ │ │ │
│ Unlock XRP |<---------------------| Return wXRP │
│ │ │ │
└─────────────────────────┘ └────────────────────────────┘
- Bridge: A method of moving assets from one blockchain to another.
- Locking chain: The chain on which the assets originate. An asset is locked on this chain before it can be represented on the issuing chain, and will remain locked while the issuing chain uses the asset.
- Issuing chain: The chain on which the assets from the locking chain are wrapped. The issuing chain issues assets that represent assets that are locked on the locking chain.
- Cross-chain transfer: A protocol that moves assets from the locking chain to the issuing chain, or returns those assets from the issuing chain back to the locking chain. This generally means that the locking chain locks and unlocks a token, while the issuing chain mints and burns a wrapped version of that token. Usually (but not always), the mainchain will be locking and unlocking a token, and the sidechain will be minting and burning the wrapped version.
- Source chain: The chain that a cross-chain transfer begins from. The transfer is from the source chain and to the destination chain.
- Destination chain: The chain that a cross-chain transfer ends at. The transfer is from the source chain and to the destination chain.
- Door account: The account on the locking chain that is used to put assets into trust, or the account on the issuing chain used to issue wrapped assets. The name comes from the idea that a door is used to move from one room to another and a door account is used to move assets from one chain to another.
- Attestation: A message signed by a witness server attesting to a particular event that happened on the other chain. This is used because chains don't talk to each other directly.
- Witness server: A server that listens for transactions on one or both of the chains and signs attestations used to prove that certain events happened on a chain.
- Cross-chain claim ID: A ledger object used to prove ownership of the funds moved in a cross-chain transfer. This object represents a unique ID for each cross-chain transfer.
A witness server is an independent server that helps provide proof that an event happened on either the locking chain or the issuing chain. It listens to transactions on one side of the bridge and submits attestations on the other side. This helps affirm that a transaction on the source chain occurred. The witness server is acting as an oracle, providing information to help prove that the assets were moved to the door account on the source chain (to be locked or burned). This then allows the recipient of those assets to claim the equivalent funds on the destination chain.
Since submitting a signature requires submitting a transaction and paying a fee, supporting rewards for signatures is an important requirement. The reward could be higher than the fee, providing an incentive for running a witness server.
This design proposes: 1 new server type, 3 new ledger objects, and 8 new transactions.
The new server type is:
- The witness server
The new ledger objects are:
Bridge
XChainOwnedClaimID
XChainOwnedCreateAccountClaimID
The new transactions are:
XChainCreateBridge
XChainModifyBridge
XChainCreateClaimID
XChainCommit
XChainAddClaimAttestation
XChainClaim
XChainAccountCreateCommit
XChainAddAccountCreateAttestation
A cross-chain transfer moves assets from the locking chain to the issuing chain, or returns those assets from the issuing chain back to the locking chain. These transfers need some primitives:
-
Put assets into trust (lock assets) on the locking chain.
-
Issue or mint wrapped assets on the issuing chain.
-
Return or burn the wrapped assets on the issuing chain.
-
On the issuing chain, prove that assets were put into trust on the locking chain.
-
On the locking chain, prove that assets were returned or burned on the issuing chain.
-
A way to prevent the same assets from being wrapped multiple times (prevent transaction replay). The proofs that certain events happened on the different chains are public and can therefore theoretically be submitted multiple times. This must be valid only once to wrap or unlock assets.
In this scenario, a user is trying to transfer funds from their account on the source chain to their account on the destination chain.
- The user creates a cross-chain claim ID on the destination chain, via the
XChainCreateClaimID
transaction. This creates aXChainOwnedClaimID
ledger object. The ledger object must specify the source account on the source chain. - The user submits a
XChainCommit
transaction on the source chain, attaching the claimed cross-chain claim ID and including a reward amount (SignatureReward
) for the witness servers. This locks or burns the asset on the source chain, depending on whether the source chain is a locking or issuing chain. This transaction must be submitted from the same account that was specified when creating the claim ID. - The witness server signs an attestation saying that the funds were locked/burned on the source chain. This is then submitted as a
XChainAddClaimAttestation
transaction on the destination chain. - When there is a quorum of witness attestations, the funds can be claimed on the destination chain. If a destination account is included in the initial transfer, then the funds automatically transfer when quorum is reached. Otherwise, the user can submit a
XChainClaim
transaction for the transferred value on the destination chain.- The rewards are then automatically distributed to the witness servers’ accounts on the destination chain.
- The witness server(s) are spun up.
- The bridge is initialized on both of the chains via
XChainCreateBridge
transactions sent by the door accounts, which creates aBridge
ledger object on each chain. Each chain has a door account that controls that end of the bridge on-chain. On one chain, currency is locked and unlocked (the locking chain). On the other chain, currency is minted and burned, or issued and reclaimed (the issuing chain).- The
Bridge
ledger object can be modified via theXChainModifyBridge
transaction.
- The
- Both chains’ door accounts set up a signer list (via a
SignerListSet
transaction) using the witness servers’ signing keys, so that they control the funds that the bridge controls. - Both chains’ door accounts disable the master key (via an
AccountSet
transaction), so that the witness servers as a collective have total control of the bridge.
Account creation must happen via a different means. This is because the cross-chain transfer process requires an existing account on the destination chain, so if the user does not have an account on the destination chain, they have no way to transfer funds.
- The user submits a
XChainAccountCreateCommit
transaction on the source chain. This locks or burns the asset on the source chain, depending on whether the source chain is a locking or issuing chain. - The witness servers each sign their attestations on the destination chain and submit them as
XChainAddAccountCreateAttestation
transactions. Since there is noXChainOwnedClaimID
object on the destination chain to keep track of the attestations, the ledger instead creates aXChainOwnedCreateAccountClaimID
object upon receiving the first attestation. - When there is a quorum of witness attestations, the funds are automatically transferred to the new account on the destination chain. The rewards are also automatically distributed to the witness servers’ accounts on the destination chain.
We propose three new objects:
-
A
Bridge
is a new object that describes a single cross-chain bridge. -
A
XChainOwnedClaimID
is a new object that describes a single cross-chain claim ID. -
A
XChainOwnedCreateAccountClaimID
is a new object that describes an account to be created on the issuing chain.
The Bridge
object represents one end of a cross-chain bridge and holds data associated with the cross-chain bridge. Bridges are created using the XChainCreateBridge
transaction.
The ledger object is owned by the door account and defines the bridge parameters. It is created with a XChainCreateBridge
transaction, and modified with a XChainModifyBridge
transaction (only the MinAccountCreateAmount
and SignaturesReward
may be changed). It cannot be deleted. A door account may not own more than one Bridge
object.
Note: The signatures used to attest to chain events are on the XChainOwnedClaimID
and XChainOwnedAccountCreateClaimID
ledger objects, not on this ledger object.
A Bridge
object has the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
LedgerIndex |
✔️ | string |
HASH256 |
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
SignatureReward |
✔️ | Currency Amount |
AMOUNT |
MinAccountCreateAmount |
Currency Amount |
AMOUNT |
|
XChainAccountCreateCount |
✔️ | number |
UINT64 |
XChainAccountClaimCount |
✔️ | number |
UINT64 |
XChainClaimID |
✔️ | number |
UINT64 |
The ledger index is a hash of a unique prefix for a bridge object, the door account for that side of the bridge, and the currency for that side of the bridge. For example, on the locking chain, the ledger index is a hash of the unique prefix for a bridge object, the locking chain door account, and the locking chain currency. The locking chain issuer is not hashed. This intentionally constrains door accounts to at most one bridge per currency type.
The bridge that this object correlates to - namely, the door accounts and the currencies.
This is the identity of the bridge and cannot be changed, as if it were to change, the object would be representing a different bridge.
It is used everywhere where a XChainBridge
field exists. This is easier for users instead of expecting them to use the ledger object ID.
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
LockingChainDoor |
✔️ | string |
ACCOUNT |
LockingChainIssue |
✔️ | Issue |
ISSUE |
IssuingChainDoor |
✔️ | string |
ACCOUNT |
IssuingChainIssue |
✔️ | Issue |
ISSUE |
LockingChainDoor
The door account on the locking chain.
LockingChainIssue
The asset that is locked and unlocked on the locking chain.
IssuingChainDoor
The door account on the issuing chain. For a XRP-XRP bridge, this must be the genesis account (the account that is created when the network is first started, which contains all of the XRP).
IssuingChainIssue
The asset that is minted and burned on the issuing chain. For an IOU-IOU bridge, the issuer of the asset must be the door account on the issuing chain, to avoid supply issues.
The total amount, in XRP, to be rewarded for providing a signature for a cross-chain transfer or for signing for the cross-chain reward. This will be split among the signers.
The minimum amount, in XRP, required for a XChainAccountCreateCommit
transaction. If this is not present, the XChainAccountCreateCommit
transaction will fail. This field can only be present on XRP-XRP bridges.
A counter used to order the execution of account create transactions. It is incremented every time a successful XChainAccountCreateCommit
transaction is run for the source chain.
A counter used to order the execution of account create transactions. It is incremented every time a XChainAccountCreateCommit
transaction is "claimed" on the destination chain. When the "claim" transaction is run on the destination chain, the XChainAccountClaimCount
must match the value that the XChainAccountCreateCount
had at the time the XChainAccountClaimCount
was run on the source chain. This orders the claims so that they run in the same order that the XChainAccountCreateCommit
transactions ran on the source chain, to prevent transaction replay.
The value of the next XChainClaimID
to be created.
The XChainOwnedClaimID
ledger object represents a unique ID for each cross-chain transfer. It must be acquired on the destination chain before submitting a XChainCommit
on the source chain. Its purpose is to prevent transaction replay attacks and is also used as a place to collect attestations from witness servers.
A XChainCreateClaimID
transaction is used to create a new XChainOwnedClaimID
. The ledger object is destroyed when the funds are successfully claimed on the destination chain.
A XChainOwnedClaimID
object may have the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
LedgerIndex |
✔️ | string |
HASH256 |
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
OtherChainSource |
✔️ | string |
ACCOUNT |
SignatureReward |
✔️ | Currency Amount |
AMOUNT |
XChainClaimAttestations |
✔️ | array |
ARRAY |
XChainClaimID |
✔️ | string |
UINT64 |
The ledger index is a hash of a unique prefix for XChainOwnedClaimID
s, the actual XChainClaimID
value, and the fields in XChainBridge
.
Which bridge the XChainClaimID
correlates to.
The account that must send the corresponding XChainCommit
on the source chain. The destination may be specified in the XChainCommit
transaction, which means that if the OtherChainSource
isn't specified, another account can try to specify a different destination and steal the funds. This also allows tracking only a single set of signatures, since we know which account will send the XChainCommit
transaction.
The total amount to pay the witness servers for their signatures. It must be at least the value of SignatureReward
in the Bridge
ledger object.
Attestations collected from the witness servers. This includes the parameters needed to recreate the message that was signed, including the amount, which chain (locking or issuing), optional destination, and reward account for that signature.
See the XChainAddClaimAttestation
section for more details on what this looks like.
The unique sequence number for a cross-chain transfer.
The XChainOwnedCreateAccountClaimID
ledger object is used to collect attestations for creating an account via a cross-chain transfer. It is created when a XChainAddAccountCreateAttestation
transaction adds a signature attesting to a XChainAccountCreateCommit
transaction and the
XChainAccountCreateCount
is greater than or equal to the current XChainAccountClaimCount
on the Bridge
ledger object. It is destroyed when all the attestations have been received and the funds have transferred to the new account.
A XChainOwnedCreateAccountClaimID
object may have the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
LedgerIndex |
✔️ | string |
HASH256 |
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
XChainAccountCreateCount |
✔️ | number |
UINT64 |
XChainCreateAccountAttestations |
✔️ | array |
ARRAY |
The ledger index is a hash of a unique prefix for XChainOwnedCreateAccountClaimID
s, the
XChainAccountCreateCount
, and the fields in XChainBridge
.
Door accounts and assets.
An integer that determines the order that accounts created through cross-chain transfers must be performed. Smaller numbers must execute before larger numbers.
Attestations collected from the witness servers. This includes the parameters needed to recreate the message that was signed, including the amount, destination, signature reward amount, and reward account for that signature. With the exception of the reward account, all signatures must sign the message created with common parameters.
See the XChainAddAccountCreateAttestation
section for more details on what this looks like.
The XChainCreateBridge
transaction creates a new Bridge
ledger object. This tells one chain (whichever chain the transaction is submitted on) the details of the bridge.
The transaction must be submitted by the door account. It must also be submitted on both chains that form the bridge in order to be a valid bridge. A door account may own multiple bridge objects, subject to a few constraints.
A bridge object must be owned by either the locking door account or the issuing chain door account. It is an error to try to create both sides of the bridge on the same chain. Doing so will result in a tecDUPLICATE
error. For example, for a bridge with a locking door account of "Alice" and an issuing door account of "Bob" that locks "USD/gw" and issues "USD/bob", if Alice has already created this bridge, and Bob also tries to create this bridge on the same chain as Alice, then Bob's transaction will fail with a tecDUPLICATE
error. It might appear that this constraint allows an account to try to block bridge creation by creating the other side of bridge accounts themselves. However, this would require them to either have the secret key for the door account or allow them to replay transactions (sidechains should have network ids that prevent transaction replay).
A second constraint is a door can own at most one bridge per currency type. For example, a door account may own a bridge that locks USD/gw and a second bridge that issues EUR/door. However, a door account may not own a bridge that lock USD/gw and a second bridge that issues USD/door. The reason for this constraint is a trust line represents a net balance between two accounts. This can cause the invariant to be violated. Assume the door account that has locked 100USD/gw. Now consider what would happen if a cross chain transaction sent 100USD/door to the gw account. The trust line would have a balance of zero! This would cause future cross chain transactions to fail for lack of funds. To avoid this scenario, the door account may have at most one bridge per currency. This applies to be both the locking and issuing side.
For an IOU-IOU bridge, the issuer of the IOU cannot have the lsfAllowTrustLineClawback
set. Wrapped funds must always be backed by locked funds and clawback would break that invariant. If the flag is set the transaction will fail with tecNO_PERMISSION
.
The XChainCreateBridge
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
SignatureReward |
✔️ | Currency Amount |
AMOUNT |
MinAccountCreateAmount |
Currency Amount |
AMOUNT |
Which bridge (door accounts and issues) to create.
The signature reward split between the witnesses for submitting attestations.
The minimum amount, in XRP, required for a XChainAccountCreateCommit
transaction. If this is not present, the XChainAccountCreateCommit
transaction will fail. This field can only be present on XRP-XRP bridges.
The XChainModifyBridge
transaction allows bridge managers to modify the parameters of the bridge. They can only change the SignatureReward
and the MinAccountCreateAmount
. This is because changing the door accounts or assets would essentially be creating a new bridge as opposed to modifying an existing one.
Note that this is a regular transaction that is sent by the door account and requires the entities that control the witness servers to coordinate and provide the signatures for this transaction. This coordination happens outside the ledger.
Note that the signer list for the bridge is not modified through this transaction. The signer list is on the door account itself and is changed in the same way signer lists are changed on accounts (via a SignerListSet
transaction).
The XChainModifyBridge
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
SignatureReward |
Currency Amount |
AMOUNT |
|
MinAccountCreateAmount |
Currency Amount |
AMOUNT |
|
Flags |
✔️ | number |
UINT32 |
Which bridge (door accounts and issues) to modify.
The signature reward split between the witnesses for submitting attestations.
The minimum amount, in XRP, required for a XChainAccountCreateCommit
transaction. If this is not present, the XChainAccountCreateCommit
transaction will fail. This field can only be present on XRP-XRP bridges.
Specifies the flags for this transaction. In addition to the universal transaction flags that are applicable to all transactions (e.g., tfFullyCanonicalSig
), the following transaction-specific flags are defined:
Flag Name | Flag Value | Description |
---|---|---|
tfClearAccountCreateAmount |
0x00010000 |
Clears the MinAccountCreateAmount of the bridge. |
The XChainCreateClaimID
transaction is the first step in a cross-chain transfer. The claim ID must be created on the destination chain before the XChainCommit
transaction (which must reference this number) can be sent on the source chain. The account that will send the XChainCommit
on the source chain must be specified in this transaction (see note on the OtherChainSource
field in the XChainOwnedClaimID
ledger object for
justification). The actual claim ID must be retrieved from a validated ledger.
The XChainCreateClaimID
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
SignatureReward |
✔️ | Currency Amount |
AMOUNT |
OtherChainSource |
✔️ | string |
ACCOUNT |
Which bridge to create the XChainOwnedClaimID
for.
The amount, in XRP, to be used to reward the witness servers for providing signatures. This must match the amount on the Bridge
ledger object.
This could be optional, but it is required so the sender can be made positively aware that these funds will be deducted from their account.
The account that must send the XChainCommit
transaction on the source chain.
Since the destination may be specified in the XChainCommit
transaction, if the OtherChainSource
wasn't specified, another account could try to specify a different destination and steal the funds.
This also allows us to limit the number of attestations that would be considered valid, since we know which account will send the XChainCommit
transaction.
The XChainCommit
transaction is the second step in a cross-chain transfer. It puts assets into trust on the locking chain so that they may be wrapped on the issuing chain, or burns wrapped assets on the issuing chain so that they may be returned on the locking chain.
The XChainCommit
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
XChainClaimID |
✔️ | string |
UINT64 |
Amount |
✔️ | Currency Amount |
AMOUNT |
OtherChainDestination |
string |
ACCOUNT |
Which bridge to use to transfer funds.
The unique integer ID for a cross-chain transfer.
This must be acquired on the destination chain (via a XChainCreateClaimID
transaction) and checked from a validated ledger before submitting this transaction.
If an incorrect sequence number is specified, the funds will be lost.
The asset to commit, and the quantity.
This must match the door account's LockingChainIssue
(if on the locking chain) or the door account's IssuingChainIssue
(if on the issuing chain).
The destination account on the destination chain.
If this is not specified, the account that submitted the XChainCreateClaimID
transaction on the destination chain will need to submit a XChainClaim
transaction to claim the funds.
The XChainAddClaimAttestation
transaction provides an attestation from a witness server attesting to a XChainCommit
transaction on the other chain.
The signature must be from one of the keys on the door's signer list at the time the signature was provided. However, if the signature list changes between the time the signature was submitted and the quorum is reached, the new signature set is used and some of the currently collected signatures may be removed. Note that the reward is only sent to accounts that have keys on the current list.
Note: Any account can submit signatures. This is important to support witness servers that work on the "subscription" model (see the Witness Server
section for more details).
Note: A quorum of signers need to agree on the SignatureReward
, the same way they need to agree on the other data. A single witness server cannot provide an incorrect value for this in an attempt to collect a larger reward.
The XChainAddClaimAttestation
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
Amount |
✔️ | Currency Amount |
AMOUNT |
AttestationRewardAccount |
✔️ | string |
ACCOUNT |
AttestationSignerAccount |
✔️ | string |
ACCOUNT |
Destination |
string |
ACCOUNT |
|
OtherChainSource |
✔️ | string |
ACCOUNT |
PublicKey |
✔️ | string |
BLOB |
Signature |
✔️ | string |
BLOB |
WasLockingChainSend |
✔️ | number |
UINT8 |
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
XChainClaimID |
✔️ | string |
UINT64 |
The amount committed via the XChainCommit
transaction on the source chain.
The account that should receive this signer's share of the SignatureReward
.
The account on the door account's signer list that is signing the transaction. Normally, the public key would be equivalent to the master key of the account. However, there is also the option of creating the account on the chain and setting a regular key, and using that key to sign attestations on behalf of the signer account.
The destination account for the funds on the destination chain (taken from the XChainCommit
transaction).
The account on the source chain that submitted the XChainCommit
transaction that triggered the event associated with the attestation.
The public key used to verify the attestation signature.
The signature attesting to the event on the other chain.
A boolean representing the chain where the event occurred.
The bridge associated with the attestations.
The XChainClaimID
associated with the transfer, which was included in the XChainCommit
transaction.
To add an attestation to a XChainOwnedClaimID
, that ledger object must already exist.
The XChainClaim
transaction allows the user to claim funds on the destination chain from a XChainCommit
transaction.
This is normally not needed, but may be used to handle transaction failures or if the destination account was not specified in the XChainCommit
transaction. It may only be used after a quorum of signatures have been sent from the witness servers.
If the transaction succeeds in moving funds, the referenced XChainOwnedClaimID
ledger object will be destroyed. This prevents transaction replay. If the transaction fails, the XChainOwnedClaimID
will not be destroyed and the transaction may be re-run with different parameters.
The XChainClaim
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
XChainClaimID |
✔️ | string |
UINT64 |
Destination |
✔️ | string |
ACCOUNT |
DestinationTag |
int |
UINT32 |
|
Amount |
✔️ | Currency Amount |
AMOUNT |
The bridge associated with this transfer.
The unique integer ID for a cross-chain transfer that was referenced in the relevant XChainCommit
transaction.
The destination account on the destination chain. It must exist or the transaction will fail. However, if the transaction fails in this case, the sequence number and collected signatures will not be destroyed and the transaction may be rerun with a different destination address.
An integer destination tag.
The amount to claim on the destination chain.
This must match the amount attested to on the attestations associated with this XChainClaimID
.
Since the cross-chain transfer process requires an existing account on the destination chain, if the user does not have an account on the destination chain, they have no way to transfer funds. With a sidechain, there are no user accounts when the chain is first created. Therefore, account creation requires a special mechanism and special transactions.
The XChainAccountCreateCommit
transaction is a special transaction used for creating accounts through a cross-chain transfer.
A normal cross-chain transfer requires a XChainClaimID
(which requires an existing account on the destination chain). One purpose of the XChainClaimID
is to prevent transaction replay. For this transaction, we use a different mechanism: the accounts must be claimed on the destination chain in the same order that the XChainAccountCreateCommit
transactions occurred on the source chain.
This transaction can only be used for XRP-XRP bridges.
IMPORTANT: This transaction should only be enabled if the witness attestations will be reliably delivered to the destination chain. If the signatures are not delivered, then account creation will be blocked until the one waiting on attestations receives its attestations. This could be used maliciously. To disable this transaction on XRP-XRP bridges, the bridge's MinAccountCreateAmount
should not be present.
Note: If this account already exists, the XRP is transferred to the existing account. However, note that unlike the XChainCommit
transaction, there is no error handling mechanism. If the claim transaction fails, there is no mechanism for refunds (except manually, via the witness signing keys on the door accounts' signer list). The funds are essentially permanently lost. This transaction should therefore only be used for account creation.
The XChainAccountCreateCommit
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
SignatureReward |
✔️ | Currency Amount |
AMOUNT |
Destination |
✔️ | string |
ACCOUNT |
Amount |
✔️ | Currency Amount |
AMOUNT |
The bridge to use to transfer funds.
The amount, in XRP, to be used to reward the witness servers for providing signatures.
This must match the amount on the Bridge
ledger object.
This could be optional, but it is required so the sender can be made positively aware that these funds will be deducted from their account.
The amount, in XRP, to use for account creation. This must be greater than or equal to the MinAccountCreateAmount
specified in the Bridge
ledger object.
The destination account on the destination chain.
The XChainAddAccountCreateAttestation
transaction provides an attestation from a witness server attesting to a XChainAccountCreateCommit
transaction on the other chain.
The signature must be from one of the keys on the door's signer list at the time the signature was provided. However, if the signature list changes between the time the signature was submitted and the quorum is reached, the new signature set is used and some of the currently collected signatures may be removed. Note that the reward is only sent to accounts that have keys on the current list.
Note: Any account can submit signatures. This is important to support witness servers that work on the "subscription" model (see the Witness Server
section for more details).
Note: A quorum of signers need to agree on the SignatureReward
, the same way they need to agree on the other data. A single witness server cannot provide an incorrect value for this in an attempt to collect a larger reward.
The XChainAddAccountCreateAttestation
transaction contains the following fields:
Field Name | Required? | JSON Type | Internal Type |
---|---|---|---|
Amount |
✔️ | Currency Amount |
AMOUNT |
AttestationRewardAccount |
✔️ | string |
ACCOUNT |
AttestationSignerAccount |
✔️ | string |
ACCOUNT |
Destination |
✔️ | string |
ACCOUNT |
OtherChainSource |
✔️ | string |
ACCOUNT |
PublicKey |
✔️ | string |
BLOB |
Signature |
✔️ | string |
BLOB |
SignatureReward |
✔️ | Currency Amount |
AMOUNT |
WasLockingChainSend |
✔️ | number |
UINT8 |
XChainAccountCreateCount |
✔️ | string |
UINT64 |
XChainBridge |
✔️ | XChainBridge |
XCHAIN_BRIDGE |
The amount committed via the XChainAccountCreateCommit
transaction on the source chain.
The account that should receive this signer's share of the SignatureReward
.
The account on the door account's signer list that is signing the transaction. Normally, the public key would be equivalent to the master key of the account. However, there is also the option of creating the account on the chain and setting a regular key, and using that key to sign attestations on behalf of the signer account.
The destination account for the funds on the destination chain.
The account on the source chain that submitted the XChainAccountCreateCommit
transaction that triggered the event associated with the attestation.
The public key used to verify the signature.
The signature attesting to the event on the other chain.
The signature reward paid in the XChainAccountCreateCommit
transaction.
A boolean representing the chain where the event occurred.
The counter that represents the order that the claims must be processed in.
The bridge associated with the attestation.
Since XChainOwnedCreateAccountClaimID
is ordered, it's possible for this object to collect a quorum of signatures but not be able to execute yet. As a result, the witness servers are designed to always deliver attestations in order. Therefore, in practice, this object should not be able to collect a quorum of attestations without being able to execute yet.
However, if this situation should occur (e.g. via a buggy witness server), then the object will not execute yet and another attestation will need to be sent to the object in order to execute it. In this case, it is okay to send an attestation that is already on the object.
A witness server helps provide proof that some event happened on either the locking chain or the issuing chain. It does not validate transactions and does not need to coordinate with other witness servers. Instead, it listens to transactions from the chains. When it detects an event of interest on the source chain, it creates an attestation (a signed message) and uses the XChainAddClaimAttestation
or XChainAddAccountCreateAttestation
transactions to submit their attestation for the event to the destination chain. When a quorum of signatures are collected on the destination chain, the funds are released.
Since submitting a signature requires submitting a transaction and paying a fee, supporting rewards for signatures is an important requirement. The reward could be higher than the fee, providing an incentive for running a witness server.
It is possible for a witness server to provide attestations for one chain only - and it is possible for the door account on the locking chain to have a different signer's list than the door account on the issuing chain. The initial implementation of the witness server assumes it is providing attestation for both chains, however it is desirable to allow witness servers that only know about one of the chains.
The current design envisions two models for how witness servers are used:
-
The servers are completely private. They submit transactions to the chains themselves and collect the rewards themselves. Allowing the servers to be private has the advantage of greatly reducing the attack surface on these servers. They won't have to deal with adversarial input to their RPC commands, and since their ip address will be unknown, it will be hard to mount an DOS attack.
-
The witness server monitors events on a chain, but does not submit their signatures themselves. Instead, another party will pay the witness server for their signature (for example, through a subscription fee), and the witness server allows that party to collect the signer's reward. The account that receives the signature reward is part of the message that the witness server signs.
Note: since submitting a signature requires submitting a transaction and paying a fee, supporting rewards for signatures is an important requirement. Of course, the reward can be higher than the fee, providing an incentive to run a witness server.
The witness server code is currently available here. However, the official repo will likely move to the XRPLF Github org if this proposal is accepted.
The witness configuration is stored in a file called witness.json
.
Field Name | Required? | JSON Type |
---|---|---|
LockingChain |
✔️ | object |
IssuingChain |
✔️ | object |
RPCEndpoint |
✔️ | object |
LogFile |
✔️ | string |
LogLevel |
✔️ | string |
DBDir |
✔️ | string |
SigningKeySeed |
✔️ | string |
SigningKeyType |
✔️ | string |
XChainBridge |
✔️ | XChainBridge |
The parameters for interacting with the locking chain.
Field Name | Required? | JSON Type |
---|---|---|
Endpoint |
✔️ | object |
TxnSubmit |
✔️ | object |
RewardAccount |
✔️ | string |
The websocket endpoint of a rippled
node synced with the locking chain.
The fields are as follows:
Field Name | Required? | JSON Type |
---|---|---|
IP |
✔️ | string |
Port |
✔️ | string |
IP
The IP address of the rippled
node.
Note: This does not accept URLs right now.
Port
The port used for the websocket endpoint.
The parameters for transaction submission on the locking chain.
Field Name | Required? | JSON Type |
---|---|---|
ShouldSubmit |
✔️ | boolean |
SigningKeySeed |
string |
|
SigningKeyType |
string |
|
SubmittingAccount |
string |
ShouldSubmit
A boolean indicating whether or not the witness server should submit transactions on the locking chain.
For the subscription model, the witness server should not submit transactions.
SigningKeySeed
The seed that the witness server should use to sign its transactions on the locking chain. This is required if ShouldSubmit
is true
.
SigningKeyType
The algorithm used to encode the SigningKeySeed
. The options are secp256k1
and ed25519
. This is required if ShouldSubmit
is true
.
SubmittingAccount
The account from which the XChainAddClaimAttestation
and XChainAddAccountCreateAttestation
transactions should be sent. This is required if ShouldSubmit
is true
.
The account that should receive the witness's share of the SignatureReward
on the locking chain.
The parameters for interacting with the issuing chain. This is identical to the LockingChain
section.
The endpoint for RPC requests to the witness server.
Field Name | Required? | JSON Type |
---|---|---|
IP |
✔️ | string |
Port |
✔️ | string |
The IP address for the endpoint.
The port for the endpoint.
The location of the log file.
The level of logs to store in the log file. The options are ["All", "Trace", "Debug", "Info", "Warning", "Error", "Fatal", "Disabled","None"]
.
The location of the directory where the databases are stored.
The seed that the witness server should use to sign its attestations.
The algorithm used to encode the SigningKeySeed
. The options are secp256k1
and ed25519
.
The bridge that the witness server is monitoring. This is identical to the XChainBridge
params in every transaction and ledger object.
After downloading and building the code, run:
./xbridge_witnessd --conf witness.json
Further details are available in the README of the repo.
The witness server is pretty lightweight, so it likely doesn't need very high specs. However, this has not been tested.
In a production environment, a witness server should use different keys for each of signing attestations, signing locking chain transactions, and signing issuing chain transactions. This adds security and helps avoid transaction replay attack issues.
Setting up a new issuing chain with a XRP-XRP bridge is somewhat complex, because there are no accounts on the issuing chain, even for witnesses. As a result, witnesses cannot directly submit XChainAddClaimAttestation
or XChainAddAccountCreateAttestation
transactions.
Once the new network is set up and active (in other words, the validators are running and are successfully closing ledgers):
- Ensure that the witnesses' transaction submission accounts are funded on the locking chain (if applicable).
- Submit a
XChainCreateBridge
transaction on the locking chain from the door account.- This should be done before the
SignerListSet
for ease of setup. - The
MinAccountCreateAmount
value should be at minimum the account reserve on the issuing chain.
- This should be done before the
- Submit a
SignerListSet
transaction on the locking chain from the door account, with the witnesses' signing keys as the signers. - Disable the master key on the locking chain's door account with an
AccountSet
transaction. - Submit a
XChainCreateBridge
transaction on the issuing chain from the door account. This door account already exists, since it must be the genesis account. - Submit
XChainAccountCreateCommit
transactions from account(s) that have enough funds, to create each of the witnesses' submission accounts on the issuing chain. - Create an attestation for each of the account create attestations for the
XChainAccountCreateCommit
transactions in step 6. These attestations should be signed by the genesis seed, since that is currently what controls the genesis account.- This could also be done via a witness server set up to not submit transactions, but it is easier to do by hand.
- Submit a
XChainAddAccountCreateAttestation
transaction for each of the attestations from step 7 on the issuing chain, with the genesis account. - Submit a
SignerListSet
transaction on the issuing chain from the door account, with the witnesses' signing keys as the signers. - Disable the master key on the issuing chain's door account with an
AccountSet
transaction.
The bridge is now set up and can be used normally.
Any two networks that have an IOU-IOU bridge between them should already have a XRP-XRP bridge between them, as a sidechain will need XRP for account reserves and transaction fees (while this could be changed, in that case the XChainBridge
amendment code could also be modified). This makes it simpler to set up an IOU-IOU bridge.
To set up an IOU-IOU bridge:
- Ensure that the witnesses' transaction submission accounts are funded on the locking chain and issuing chain (if applicable).
- Submit a
XChainCreateBridge
transaction on the locking chain from the door account.- This should be done before the
SignerListSet
for ease of setup. - The
MinAccountCreateAmount
value should not be included.
- This should be done before the
- Submit a
SignerListSet
transaction on the locking chain from the door account, with the witnesses' signing keys as the signers. - Disable the master key on the locking chain's door account with an
AccountSet
transaction. - Submit a
XChainCreateBridge
transaction on the issuing chain from the door account. - Submit a
SignerListSet
transaction on the issuing chain from the door account, with the witnesses' signing keys as the signers. - Disable the master key on the issuing chain's door account with an
AccountSet
transaction.
The bridge is now set up and can be used normally.
The witness servers are trusted, and if a quorum of them collude they can steal funds from the door account.
The public keys that the witness servers use must match the public keys on that door's signer's list. But this isn't the only way to implement this. A bridge ledger object could contain a signer list that's independent from the door account. The reasons for using the door account's signers list are:
- The bridge signers list can be used to move funds from the account. Putting this list on the door account emphasizes this trust model.
- It allows for emergency action. If something goes very, very wrong, funds could still be moved if the entities on the signer list sign a regular
Payment
transaction. - It's a more natural way to modify bridge parameters.
Normally, account sequence numbers prevent transaction replay on the XRP Ledger. However, this bridge design allows funds to move from an account via transactions not sent by that account (namely, the attestations submitted by the witness servers). All the information to replay these transactions are publicly available. This section describes how the different transactions prevent certain attacks - including transaction replay attacks.
To successfully run a XChainClaim
transaction, the account sending the transaction must own the XChainOwnedClaimID
ledger object referenced in the witness server's attestation. Since this ledger object is destroyed when the funds are successfully moved, the transaction cannot be replayed.
To successfully create an account with the XChainAccountCreateCommit
transaction, the ordering number must match the current order number on the bridge ledger object. After the transaction runs, the order number on the bridge ledger object is incremented. Since this number is incremented, the transaction can not be replayed since the order number in the transaction will never match again.
Since the XChainCommit
can contain an optional destination account on the destination chain, and the funds will move when the destination chain collects enough signatures, one attack would be for an account to watch for a XChainCommit
to be sent and then send their own XChainCommit
for a smaller amount. This attack doesn't steal funds, but it does result in the original sender losing their funds. To prevent this, when a XChainOwnedClaimID
is created on the destination chain, the account that will send the XChainCommit
on the source chain must be specified. Only the attestations from this transaction will be accepted on the XChainOwnedClaimID
.
Error handling for cross-chain transfers is straightforward. The XChainOwnedClaimID
is only destroyed when a claim succeeds. If it fails for any reason (for example, if the destination account doesn't exist or has deposit auth set), then an explicit XChainClaim
transaction may be submitted to redirect the funds.
If a cross-chain account create fails, the recovery of funds must happen outside the rules of the bridge system. The only way to recover them would be if the witness servers created a Payment
transaction themselves. This is unlikely to happen and should not be relied upon. The MinAccountCreateAmount
on the Bridge
object is meant to prevent these transactions from failing due to having too little XRP.
If the signature reward cannot be delivered to the specified account, that portion of the signature reward is kept by the account that owns the XChainOwnedClaimID
.
A proposed implementation is available here: XRPLF/rippled#4292
An in-development version of a witness server is available here: https://github.com/seelabs/xbridge_witness