From 4a23747d3a8244076356c24c9b50e7957899c1d4 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 28 Jun 2023 06:47:37 -0400 Subject: [PATCH] adding white papers as improvement proposals --- docs/SUMMARY.md | 14 ++ docs/whips/0001_generic_message_passing.md | 252 +++++++++++++++++++++ docs/whips/0002_governance_messaging.md | 79 +++++++ docs/whips/0003_token_bridge.md | 249 ++++++++++++++++++++ docs/whips/0004_message_publishing.md | 124 ++++++++++ docs/whips/0005_data_availability.md | 149 ++++++++++++ docs/whips/0006_nft_bridge.md | 159 +++++++++++++ docs/whips/0007_governor.md | 75 ++++++ docs/whips/0008_batch_messaging.md | 156 +++++++++++++ docs/whips/0009_guardian_key.md | 37 +++ docs/whips/0010_integrity_checkers.md | 45 ++++ docs/whips/0011_accountant.md | 190 ++++++++++++++++ docs/whips/0012_ibc_generic_messaging.md | 90 ++++++++ 13 files changed, 1619 insertions(+) create mode 100644 docs/whips/0001_generic_message_passing.md create mode 100644 docs/whips/0002_governance_messaging.md create mode 100644 docs/whips/0003_token_bridge.md create mode 100644 docs/whips/0004_message_publishing.md create mode 100644 docs/whips/0005_data_availability.md create mode 100644 docs/whips/0006_nft_bridge.md create mode 100644 docs/whips/0007_governor.md create mode 100644 docs/whips/0008_batch_messaging.md create mode 100644 docs/whips/0009_guardian_key.md create mode 100644 docs/whips/0010_integrity_checkers.md create mode 100644 docs/whips/0011_accountant.md create mode 100644 docs/whips/0012_ibc_generic_messaging.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index d6980a3c..8be80228 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -49,6 +49,20 @@ * [See Also](./reference/see-also.md) * [Ecosystem](https://wormhole.com/ecosystem) +## Wormhole Improvment Proposals + +* [Generic Message Passing](./whips/0001_generic_message_passing.md) +* [Governance Messaging](./whips/0002_governance_messaging.md) +* [Token Bridge](./whips/0003_token_bridge.md) +* [Message Publishing](./whips/0004_message_publishing.md) +* [Data Availability](./whips/0005_data_availability.md) +* [NFT Bridge](./whips/0006_nft_bridge.md) +* [Governor](./whips/0007_governor.md) +* [Guardian Key](./whips/0009_guardian_key.md) +* [Integrity Checkers](./whips/0010_integrity_checkers.md) +* [Accountant](./whips/0011_accountant.md) +* [IBC Generic Messaging](./whips/0012_ibc_generic_messaging.md) + ## Archive diff --git a/docs/whips/0001_generic_message_passing.md b/docs/whips/0001_generic_message_passing.md new file mode 100644 index 00000000..58580f8a --- /dev/null +++ b/docs/whips/0001_generic_message_passing.md @@ -0,0 +1,252 @@ +# Generic Message Passing + +[TOC] + +## Objective + +To refactor Wormhole into a fully generic cross chain messaging protocol and remove any application-specific +functionality from the core protocol. + +## Background + +Wormhole was originally designed to support a very specific kind of cross-chain message passing - token wrapping/swaps +between Solana and Ethereum. Read more about the original design and its goals in +the [announcement blog post](https://medium.com/certus-one/introducing-the-wormhole-bridge-24911b7335f7) and +the [protocol documentation](https://github.com/wormhole-foundation/wormhole/blob/48b3c0a3f8b35818952f61c38d89850eb8924b55/docs/protocol.md) + +Since then, it has become clear that there is strong demand for using Wormhole's simple cross-chain state attestation +model for applications beyond its original design. This includes third-party projects wanting to transfer tokens other +than ERC20 (like NFTs), transfers guaranteed by insurance pools, "slow path/fast path" designs, as well as entirely +different use cases like oracles broadcasting arbitrary data to multiple chains. + +Enabling these use cases requires extending Wormhole to provide a generic set of APIs and design patterns, decoupling it +from the application logic. + +The core problem that both the current and future Wormhole design is solving is that of **enabling contracts on one +chain to verify messages from a different chain**. Smart contract engines on chains are often insufficiently powerful to +independently verify expensive state proofs from other chains due to the amount of storage and compute required. They +therefore need to rely on off-chain oracles to observe and verify messages and then re-sign them such that they _can_ be +verified on any of the connected chains, by trusting the oracle network as an intermediary rather than trusting the +remote chain. + +We previously designed a similar protocol extension for the current Wormhole design, called EE-VAAs, which is the +precursor to this fully generic design: + +- [External Entity VAAs](https://github.com/wormhole-foundation/wormhole/issues/147) +- [External Entity: Account State Attestation](https://github.com/wormhole-foundation/wormhole/issues/149) +- [External Entity: Relayer mode](https://github.com/wormhole-foundation/wormhole/issues/150) + +This design doc assumes basic familiarity with the current design of Wormhole. + +## Goals + +We want to enable a wider range of both 1:1 and 1:n messaging applications to be built on Wormhole without requiring +changes to the core protocol for each new use case. Some examples of such applications that third parties could build: + +- Unicast messaging between two specific contracts on different chains (example: token or NFT swaps). + +- Multicast from a single chain to a specific set of connected chains (example: relaying data published by price oracles + like Pyth or Chainlink). + +The goal is to **redesign the protocol such that it is fully decoupled from the application logic**. This means that +Wormhole will no longer hold assets in custody or interact with any tokens other than providing the low-level protocol +which protocols interacting with tokens could be built on top of. This includes message delivery - Wormhole's current +design directly delivered messages to a target contract for some chains. With a generic protocol, the delivery mechanism +can wildly differ between different use cases. + +## Non-Goals + +This design document focuses only on the mechanics of the message passing protocol and does not attempt to solve the +following problems, leaving them for future design iterations: + +- The specifics of implementing applications, other than ensuring we provide the right APIs. + +- Data availability/persistence. Delivering the signed message to the target chain is up to the + individual application. Possible implementations include client-side message retrieval and submission, like the + current Wormhole implementation does for delivering transfer messages on Ethereum, or message relays. + +- The mechanics of economically incentivizing nodes to maintain uptime and not to censor or forge messages. + +- Governance and criteria for inclusion in the guardian set. We only specify the governance API without defining its + implementation, which could be a smart contract on one of the connected chains. + +## Overview + +We simplify the design of Wormhole to only provide generic **signed attestations of finalized chain state**. +Attestations can be requested by any contract by publishing a message, which is then picked up and signed by the +Wormhole guardian set. The signed attestation will be published on the Wormhole P2P network. + +Delivering the message to a contract on the target chain is shifted to the higher-layer protocol. + +## Detailed Design + +The new, generic VAA struct would look this this: + +```go +// VAA is a verifiable action approval of the Wormhole protocol. +// It represents a message observation made by the Wormhole network. +VAA struct { + // -------------------------------------------------------------------- + // HEADER - these values are not part of the observation and instead + // carry metadata used to interpret the observation. It is not signed. + + // Protocol version of the entire VAA. + Version uint8 + + // GuardianSetIndex is the index of the guardian set that signed this VAA. + // Signatures are verified against the public keys in the guardian set. + GuardianSetIndex uint32 + + // Number of signatures included in this VAA + LenSignatures uint8 + + // Signatures contain a list of signatures made by the guardian set. + Signatures []*Signature + + // -------------------------------------------------------------------- + // OBSERVATION - these fields are *deterministically* set by the + // Guardian nodes when making an observation. They uniquely identify + // a message and are used for replay protection. + // + // Any given message MUST NEVER result in two different VAAs. + // + // These fields are part of the signed digest. + + // Timestamp of the observed message (for most chains, this + // identifies the block that contains the message transaction). + Timestamp time.Time + + // Nonce of the VAA, must to be set to random bytes. Nonces + // prevent collisions where one emitter publishes identical + // messages within one block (= timestamp). + // + // It is not suitable as a global identifier - + // use the (chain, emitter, sequence) tuple instead. + Nonce uint32 // <-- NEW + + // EmitterChain the VAA was emitted on. Set by the guardian node + // according to which chain it received the message from. + EmitterChain ChainID // <-- NEW + + // EmitterAddress of the contract that emitted the message. Set by + // the guardian node according to protocol metadata. + EmitterAddress Address // <-- NEW + + // Sequence number of the message. Automatically set and + // and incremented by the core contract when called by + // an emitter contract. + // + // Tracked per (EmitterChain, EmitterAddress) tuple. + Sequence uint64 // <-- NEW + + // Level of consistency requested by the emitter. + // + // The semantic meaning of this field is specific to the target + // chain (like a commitment level on Solana, number of + // confirmations on Ethereum, or no meaning with instant finality). + ConsistencyLevel uint8 // <-- NEW + + // Payload of the message. + Payload []byte // <-- NEW +} + +// ChainID of a Wormhole chain. These are defined in the guardian node +// for each chain it talks to. +ChainID uint8 + +// Address is a Wormhole protocol address. It contains the native chain's address. +// If the address data type of a chain is < 32 bytes, the value is zero-padded on the left. +Address [32]byte + +// Signature of a single guardian. +Signature struct { +// Index of the validator in the guardian set. +Index uint8 +// Signature bytes. +Signature [65]byte +} +``` + +The previous `Payload` method and `BodyTransfer`/`BodyGuardianSetUpdate`/`BodyContractUpgrade` structs with fields +like `TargetChain`, `TargetAddress`, `Asset`and `Amount` will be removed and replaced by top-level `EmitterChain` +and `EmitterAddress` fields and an unstructured `Payload` blob. To allow for ordering on the receiving end, `Sequence` +was added which is a message counter tracked per emitter. + +Notably, we remove target chain semantics, leaving it as an implementation detail for a higher-level relayer protocol. + +Guardian set updates and contract upgrades will still be handled and special-cased at the Wormhole contract layer. +Instead of specifying a VAA payload type like we previously did, Wormhole contracts will instead be initialized with a +specific well-known `EmitterChain` and `EmitterAddress` tuple which is authorized to execute governance operations. +Governance operations are executed by calling a dedicated governance method on the contracts. + +All contracts will be expected to support online upgrades. This implies changes to the Ethereum and Terra contracts to +make them upgradeable. + +## Related Technologies + +In this section, Wormhole is compared to related technologies on the market. We have carefully evaluated all existing +solutions to ensure that we have selected a unique set of trade-offs and are not reinventing any wheels. + +### Cosmos Hub and IBC + +The [IBC protocol](https://ibcprotocol.org/documentation), famously implemented by the Cosmos SDK, occupies a similar +problem space as Wormhole - cross-chain message passing. It is orthogonal to Wormhole and solves a larger and +differently shaped problem, leading to a different design. + +IBC specifies a cross-chain communication protocol with high-level semantics like channels, ports, acknowledgements, +ordering and timeouts. It is a stream abstraction on top of a packet/datagram transport, vaguely similar to the TCP/IP +protocol. IBC is part of the Cosmos Internet of Blockchain scalability vision, with hundreds or even thousands of +sovereign IBC-compatible chains (called "zones") communicating via IBC using a hub-and-spoke topology. Data availability +is provided by permissionless relayers. + +With IBC, for two chains to communicate directly with each other, they would have to be able to prove state mutually. +This usually means implementing light clients for the other chain. In modern pBFT chains like those based +on [Tendermint](https://v1.cosmos.network/resources/whitepaper) consensus, verifying light client proofs +is [very cheap](https://blog.cosmos.network/light-clients-in-tendermint-consensus-1237cfbda104) - all that is needed is +to follow validator set changes, instead of a full header chain. However, chains talking to each other directly would +get unmanageable with many chains - and this is where central hubs like Cosmos Hub come in. Instead of every individual +chain discovering and validating proofs of every other chain, instead, it can choose trust a single chain - the Hub - +which then runs light clients for every chain it is connected to. This requires the hub to have a very high degree of +security, which is why the Cosmos Hub has its own token - $ATOM - which now has a billion-dollar market cap. + +IBC works best when connecting modern pBFT chains that implement the IBC protocol and whose light client proofs are +cheap to verify. + +This is not the case for chains like Ethereum or Solana. Ethereum requires a lot of state - the full header chain - to +verify inclusion proofs. This is too expensive to do on the Hub, or any individual Cosmos chain, so a proxy chain ( +called a "peg zone") instead verifies the proofs, similarly to Wormhole. The peg zone would have its own security and +validator set just like any other zone, and vouches for the Ethereum state. + +See [Gravity](https://github.com/cosmos/gravity-bridge) for how an Ethereum peg zone would look like. It's possible to +verify Cosmos light client proofs on Ethereum, but not vice versa - the peg zone validators are trusted just like +Wormhole nodes, and use a multisig mechanism similar to Wormhole for messages sent to Ethereum. + +Solana does not currently provide a light client implementation, but like Ethereum, any Solana light client would also +need a [large amount of state](https://docs.solana.com/proposals/simple-payment-and-state-verification) to verify +inclusion proofs due to the complexity of the Solana consensus. + +Instead of connecting hundreds of IBC-compatible chains with a few non-IBC outliers with peg zones, Wormhole is designed +to **connect a low number of high-value DeFi chains**, most of which do not support IBC, which results in a different +design. + +A peg zone is the closest analogy to Wormhole in the IBC model, with some important differences: + +- Wormhole is a lower-level building block than IBC and specifies no high-level semantics like connections or target + chains, leaving this to higher-layer protocols (think "Ethernet", not "TCP/IP"). This is more flexible and less + complex to implement and audit, and moves the complexity to the upper layer and libraries only where it is needed. + +- Instead of operating our own Layer 1 proof-of-stake chain, we rely on finality of the connected chains. A staking + mechanism for Wormhole guardian nodes would be managed by a smart contract on one of those chains and inherit its + security properties. Nodes cannot initiate consensus on their own. + +- By only reacting to finalized state on chains, each with strong finality guarantees, the Wormhole protocol does not + need complex consensus, finality or leader election. It signs _observations_ of finalized state, which all nodes do + synchronously, and broadcasts them to a peer-to-peer network. There's no possibility of equivocation or eclipse + attacks leading to disagreements. + +- Long-range attacks and other PoS attacks are prevented by guardian set update finality on the connected chains. After + a brief convergence window, the old guardian set becomes invalid and no alternative histories can be built. + +- Instead of relying on inclusion proofs, we use a multisig scheme which is easier to understand and audit and cheaper + to verify on all connected chains. The extra guarantees offered by an inclusion proof are not needed in the Wormhole + network, since it merely shuttles data between chains, each of which have provable and immutable history. diff --git a/docs/whips/0002_governance_messaging.md b/docs/whips/0002_governance_messaging.md new file mode 100644 index 00000000..86a0bd8a --- /dev/null +++ b/docs/whips/0002_governance_messaging.md @@ -0,0 +1,79 @@ +# Cross-Chain Governance Decision Messaging + +[TOC] + +## Objective + +Establish a protocol for wormhole core implementations and modules on different chains to communicate governance +decisions/instructions with each other. + +## Goals + +- Define a messaging protocol for global and chain-specific governance actions. +- A message should carry all required information required for an implementation to implement it. + +## Non-Goals + +- Define the governance processes itself (staking, voting etc.) + +## Overview + +Governance happens in a smart contract on Solana (to be specified). This contract passes VAAs with finalized decisions to the Wormhole. + +Implementations on other chains have the address of that contract hardcoded and accept a set of VAAs for governance actions from that contract. +All governance VAAs follow the `GovernancePacket` structure. + +### General Packet Structure + +`Module` is the component this governance VAA is targeting. This could be the core bridge contract but any +program (e.g. a Wormhole extension module) can use the governance contract and governance messaging by picking a unique +identifier. + +`Action` is a unique action ID that identify different governance message payloads of a module. + +```go +GovernancePacket struct { + // Module identifier (left-padded) + Module [32]byte + // Action index + Action uint8 + // Chain index (0 for non-specific actions like guardian set changes) + Chain uint16 + // Action-specific payload fields + [...] +} +``` + +### Specified Governance VAAs + +The following VAAs are example governance VAAs of the core Wormhole contract. + +```go +// ContractUpgrade is a VAA that instructs an implementation on a specific chain to upgrade itself +ContractUpgrade struct { + // Core Wormhole Module + Module [32]byte = "Core" + // Action index (1 for Contract Upgrade) + Action uint8 = 1 + // Target chain ID + Chain uint16 + // Address of the new Implementation + NewContract [32]byte +} + +// GuardianSetUpgrade is a VAA that instructs an implementation to upgrade the current guardian set +GuardianSetUpgrade struct { + // Core Wormhole Module + Module [32]byte = "Core" + // Action index (2 for GuardianSet Upgrade) + Action uint8 = 2 + // This update is chain independent + Chain uint16 = 0 + + // New GuardianSet + NewGuardianSetIndex uint32 + // New GuardianSet + NewGuardianSetLen u8 + NewGuardianSet []Guardian +} +``` diff --git a/docs/whips/0003_token_bridge.md b/docs/whips/0003_token_bridge.md new file mode 100644 index 00000000..4c1eecd3 --- /dev/null +++ b/docs/whips/0003_token_bridge.md @@ -0,0 +1,249 @@ +# Token Bridge App + +[TOC] + +## Objective + +To use the Wormhole message passing protocol to transfer tokens between different connected chains. + +## Background + +The decentralized finance ecosystem is developing into a direction where different chains with different strengths +become the home for various protocols. However, a token is usually only minted on a single chain and therefore +disconnected from the ecosystem and protocols on other chains. + +Each chain typically has one de-facto standard for token issuance, like ERC-20 on Ethereum and SPL on Solana. Those +standards, while not identical, all implement a similar interface with the same concepts like owning, minting, +transferring and burning tokens. + +To connect chains, a token would ideally have a native mint - the original token on the chain it was originally created +on - and a wrapped version on other chains that represents ownership of the native token. + +While the Wormhole messaging protocol provides a way to attest and transfer messages between chains which could +technically be used to implement bridging for individual tokens, this would require manual engineering effort for each +token and create incompatible protocols with bad UX. + +## Goals + +We want to implement a generalized token bridge using the Wormhole message passing protocol that is able to bridge any +standards-compliant token between chains, creating unique wrapped representations on each connected chain on demand. + +* Allow transfer of standards-compliant tokens between chains. +* Allow creation of wrapped assets. +* Use a universal token representation that is compatible with most VM data types. +* Allow domain-specific payload to be transferred along the token, enabling + tight integration with smart contracts on the target chain. + +## Non-Goals + +* Support fee-burning / rebasing / non-standard tokens. +* Manage chain-specific token metadata that isn't broadly applicable to all chains. +* Automatically relay token transfer messages to the target chain. + +## Overview + +On each chain of the token bridge network there will be a token bridge endpoint program. + +These programs will manage authorization of payloads (emitter filtering), wrapped representations of foreign chain +tokens ("Wrapped Assets") and custody locked tokens. + +## Detailed Design + +For outbound transfers, the contracts will have a lock method that either locks up a native token and produces a +respective Transfer message that is posted to Wormhole, or burns a wrapped token and produces/posts said message. + +For inbound transfers they can consume, verify and process Wormhole messages containing a token bridge payload. + +There will be five different payloads: + +* `Transfer` - Will trigger the release of locked tokens or minting of wrapped tokens. +* `TransferWithPayload` - Will trigger the release of locked tokens or minting of wrapped tokens, with additional domain-specific payload. +* `AssetMeta` - Attests asset metadata (required before the first transfer). +* `RegisterChain` - Register the token bridge contract (emitter address) for a foreign chain. +* `UpgradeContract` - Upgrade the contract. + +Since anyone can use Wormhole to publish messages that match the payload format of the token bridge, an authorization +payload needs to be implemented. This is done using an `(emitter_chain, emitter_address)` tuple. Every endpoint of the +token bridge needs to know the addresses of the respective other endpoints on other chains. This registration of token +bridge endpoints is implemented via `RegisterChain` where a `(chain_id, emitter_address)` tuple can be registered. Only +one endpoint can be registered per chain. Endpoints are immutable. This payload will only be accepted if the emitter is +the hardcoded governance contract. + +In order to transfer assets to another chain, a user needs to call the `transfer` (or the `transferWithPayload`) method +of the bridge contract with the recipient details and respective fee they are willing to pay. The contract will either +hold the tokens in a custody account (in case it is a native token) or burn wrapped assets. Wrapped assets can be burned +because they can be freely minted once tokens are transferred back and this way the total supply can indicate the total +amount of tokens currently held on this chain. After the lockup the contract will post a `Transfer` (or +`TransferWithPayload`) message to Wormhole. Once the message has been signed by the guardians, it can be posted to the +target chain of the transfer. Upon redeeming (see `completeTransfer` below), the target chain will either release native +tokens from custody or mint a wrapped asset depending on whether it's a native token there. +The token bridges guarantee that there will be a unique wrapped asset on each chain for each non-native token. In other +words, transferring a native token from chain A to chain C will result in the same wrapped token as transferring from A +to B first, then from B to C, and no double wrapping will happen. +The program will keep track of consumed message digests (which include a nonce) for replay prevention. + +To redeem the transaction on the target chain, the VAA must be posted to the target token bridge. Since the VAA includes +a signature from the guardians, it does not matter in general who submits it to the target chain, as VAAs cannot be +spoofed, and the VAA includes the target address that the tokens will be sent to upon completion. +An exception to this is `TransferWithPayload`, which must be redeemed by the target address, because it contains +additional payload that must be handled by the recipient (such as token-swap instructions). +The `completeTransfer` method will accept a fee recipient. In case that field is set, the fee amount +specified will be sent to the fee recipient and the remainder of the amount to the intended receiver of the transfer. +This allows transfers to be completed by independent relayers to improve UX for users that will only need to send a +single transaction for as long as the fee is sufficient and the token accepted by anyone acting as relayer. + +In order to keep `Transfer` messages small, they don't carry all metadata of the token. However, this means that before +a token can be transferred to a new chain for the first time, the metadata needs to be bridged and the wrapped asset +created. Metadata in this case includes the amount of decimals which is a core requirement for instantiating a token. + +The metadata of a token can be attested by calling `attestToken` on its respective native chain which will produce a +`AssetMeta` wormhole message. This message can be used to attest state and initialize a WrappedAsset on any chain in the +wormhole network using the details. A token is identified by the tuple `(chain_id, chain_address)` and metadata should +be mapped to this identifier. A wrapped asset may only ever be created once for a given identifier and not updated. + +### Handling of token amounts and decimals + +Due to constrains on some supported chains, all token amounts passed through the token bridge are truncated to a maximum of 8 decimals. + +Any chains implementation must make sure that of any token only ever MaxUint64 units (post-shifting) are bridged into the wormhole network at any given time (all target chains combined), even tough the slot is 32 bytes long (theoretically fitting uint256). + +Token "dust" that can not be transferred due to truncation during a deposit needs to be refunded back to the user. + +**Examples:** +- The amount "1" of a 18 decimal Ethereum token is originally represented as: `1000000000000000000`, over the wormhole it is passed as: `100000000`. +- The amount "2" of a 4 decimal token is represented as `20000` and is passed over the wormhole without a decimal shift. + +**Handling on the target Chains:** + +Implementations on target chains can handle the decimal shift in one of the following ways: +- In case the chain supports the original decimal amount (known from the `AssetMeta`) it can do a decimal shift back to the original decimal amount. This allows for out-of-the-box interoperability of DeFi protocols across for example different EVM environments. +- Otherwise the wrapped token should stick to the 8 decimals that the protocol uses. + +### API / database schema + +Proposed bridge interface: + +`attestToken(address token)` - Produce a `AssetMeta` message for a given token + +`transfer(address token, uint64-uint256 amount (size depending on chains standards), uint16 recipient_chain, bytes32 recipient, uint256 fee)` - Initiate +a `Transfer`. Amount in the tokens native decimals. + +`transferWithPayload(address token, uint64-uint256 amount (size depending on chains standards), uint16 recipient_chain, bytes32 recipient, bytes payload)` - Initiate +a `TransferWithPayload`. Amount in the tokens native decimals. `payload` is an arbitrary binary blob. + +`createWrapped(Message assetMeta)` - Creates a wrapped asset using `AssetMeta` + +`completeTransfer(Message transfer)` - Execute a `Transfer` message + +`completeTransferWithPayload(Message transfer)` - Execute a `TransferWithPayload` message + +`registerChain(Message registerChain)` - Execute a `RegisterChain` governance message + +`upgrade(Message upgrade)` - Execute a `UpgradeContract` governance message + +--- +**Payloads**: + +Transfer: + +``` +PayloadID uint8 = 1 +// Amount being transferred (big-endian uint256) +Amount uint256 +// Address of the token. Left-zero-padded if shorter than 32 bytes +TokenAddress bytes32 +// Chain ID of the token +TokenChain uint16 +// Address of the recipient. Left-zero-padded if shorter than 32 bytes +To bytes32 +// Chain ID of the recipient +ToChain uint16 +// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount. +Fee uint256 +``` + +TransferWithPayload: + +``` +PayloadID uint8 = 3 +// Amount being transferred (big-endian uint256) +Amount uint256 +// Address of the token. Left-zero-padded if shorter than 32 bytes +TokenAddress bytes32 +// Chain ID of the token +TokenChain uint16 +// Address of the recipient. Left-zero-padded if shorter than 32 bytes +To bytes32 +// Chain ID of the recipient +ToChain uint16 +// The address of the message sender on the source chain +FromAddress bytes32 +// Arbitrary payload +Payload bytes +``` + +AssetMeta: + +``` +PayloadID uint8 = 2 +// Address of the token. Left-zero-padded if shorter than 32 bytes +TokenAddress [32]uint8 +// Chain ID of the token +TokenChain uint16 +// Number of decimals of the token +// (the native decimals, not truncated to 8) +Decimals uint8 +// Symbol of the token (UTF-8) +Symbol [32]uint8 +// Name of the token (UTF-8) +Name [32]uint8 +``` + +RegisterChain: + +``` +// Gov Header +// Module Identifier ("TokenBridge" left-padded) +Module [32]byte +// Governance Action ID (1 for RegisterChain) +Action uint8 = 1 +// Target Chain (Where the governance action should be applied) +// (0 is a valid value for all chains) +ChainId uint16 + +// Packet +// Emitter Chain ID +EmitterChainID uint16 +// Emitter address. Left-zero-padded if shorter than 32 bytes +EmitterAddress [32]uint8 +``` + +UpgradeContract: + +``` +// Header +// Module Identifier ("TokenBridge" left-padded) +Module [32]byte +// Governance Action ID (2 for UpgradeContract) +Action uint8 = 2 +// Target Chain (Where the governance action should be applied) +ChainId uint16 + +// Packet +// Address of the new contract +NewContract [32]uint8 +``` + +## Caveats + +There is no guarantee for completion of transfers. If a user initiates a transfer and doesn't call `completeTransfer` +on the target chain, a transfer might not be completed. In case a guardian set change happens in-between and the +original signer guardian set expires, the transfer will be stuck indefinitely. + +Since there is no way for a token bridge endpoint to know which other chain already has wrapped assets set up for the +native asset on its chain, there may be transfers initiated for assets that don't have wrapped assets set up yet on the +target chain. However, the transfer will become executable once the wrapped asset is set up (which can be done any time). + + + + diff --git a/docs/whips/0004_message_publishing.md b/docs/whips/0004_message_publishing.md new file mode 100644 index 00000000..45370591 --- /dev/null +++ b/docs/whips/0004_message_publishing.md @@ -0,0 +1,124 @@ +# Message Publishing + +[TOC] + +## Objective + +To specify the mechanics and interfaces for publishing messages over Wormhole. + +## Background + +The original Generic Message Passing design doc describes the format of Wormhole messages, however, the way messages are +published needs to be defined clearly for chain endpoints to be built. + +## Goals + +* Specify an interface for posting messages. +* Specify a fee model for message posting. + +## Non-Goals + +* Dynamic spam protection. +* Distribution of fees. + +## Overview + +The core Wormhole contracts on every chain will have a method for posting messages to Wormhole, which will emit an event +that needs to be observable by guardians running a full node on a given chain. + +The fees will be payable in the respective chain's native currency. Fees can be +claimed by the protocol and collected in a fee pool on Solana where they can be distributed according to protocol rules. + +## Detailed Design + +Wormhole core contracts have a `postMessage` method which can be used by EOAs (externally owned accounts) or SCs (smart +contracts) +to publish a message via Wormhole. + +This method has to perform verification on the payload for the maximum size limitation of **750 bytes**. The message +should be emitted such that it can be picked up by guardians in a way that allows offline nodes to replay missed blocks. + +The Wormhole contract will also need to make the emitter of the published message available to the guardians. The +emitter is either a parameter to the postMessage method if the chain allows proving that the caller controls or is +authorized by said address (i.e. Solana PDAs), or it is the sender of the transaction. + +Additionally, the Wormhole contract will keep track of a sequence number per emitter that is incremented for each +message submitted. + +The timestamp is derived by the guardian software using the finalized timestamp of the block the message was published +in. + +When a message is posted, the emitter can specify for how many confirmations the guardians should wait before an +attestation is produced. This allows latency sensitive applications to make sacrifices on safety while critical +applications can sacrifice latency over safety. Chains with instant finality can omit the argument. + +**Fees:** + +In order to incentivize guardians and prevent spamming of the Wormhole network, publishing a message will require a fee +payment. + +This fee is supposed to be paid in any of the chain's native fee currencies when publishing a message. This assumes that +anyone sending a transaction is already required to hold such assets in order to make the transaction publishing the +message, and that the fee will therefore not negatively affect usability of the bridge. + +The fee is defined by governance using the `SetMessageFee` VAA. The fees set are denominated in the respective chains +native currency. Each chain's Wormhole program is supposed to use an on-chain price oracle (e.g. a uniswap pool TWAP or +Pyth price feed) +Fees are set per chain to allow the protocol to take into consideration the effort required to keep the chain's nodes +online and account for spam attacks. + +Fees will be collected in a wallet that is controlled by the Wormhole contract, governance or a more automated mechanism +to be implemented in a later design doc will be able to produce a `TransferFees` which will allow to move the collected +fee tokens to a specified address. In case there is a widely accepted token bridge, this mechanism might be extended to +bridge tokens back to the chain where the governance and staking contracts are located for them to be distributed there. + +### API / database schema + +Proposed bridge interface: + +`postMessage(bytes payload, u8 confirmations)` - Publish a message to be attested by Wormhole. + +`setFees(VAA fee_payload)` - Update the fees using a `SetMessageFee` VAA + +`transferFees(VAA transfer_payload)` - Transfer fees using a `TransderFees` VAA + +--- + +**Payloads**: + +The payloads follow the governance message format. + +SetMessageFee: + +``` +// Core Wormhole Module +Module [32]byte = "Core" +// Action index (3 for Fee Update) +Action uint16 = 3 +Chain uint16 +// Message fee in the native token +Fee uint256 +``` + +TransferFees: + +``` +// Core Wormhole Module +Module [32]byte = "Core" +// Action index (4 for Fee Transfer) +Action uint16 = 4 +Chain uint16 +// Amount being transferred (big-endian uint256) +Amount uint256 +// Address of the recipient. Left-zero-padded if shorter than 32 bytes +To [32]uint8 +``` + +## Caveats + +A governance decision is required for the collection of fees. This means a lot of manual intervention in the +distribution of fees to stakers and guardians. The lack of a token bridge makes it hard to automate this in the early +days of the protocol. Also, a transfer primitive is unlikely to support token bridges (which may require smart contract +calls), so a contract will be required. + +## Security Considerations diff --git a/docs/whips/0005_data_availability.md b/docs/whips/0005_data_availability.md new file mode 100644 index 00000000..afae880f --- /dev/null +++ b/docs/whips/0005_data_availability.md @@ -0,0 +1,149 @@ +# Signed message data availability + +[TOC] + +## Objective + +To make signed messages available to Wormhole clients without relying on a connected chain. + +## Background + +A Wormhole workflow typically starts by having a user submit a transaction on any of the connected chains, which results in a message being posted on-chain. This message is then picked up and confirmed by the guardian network. Once enough guardians signed the observation, the resulting message - the signed VAA - needs to be posted to the target chain to complete the operation. + +With the Wormhole v1 design, we use Solana for data availability. As soon as any guardian observes a VAA with sufficient signatures, it would race to submit it to Solana, where it would be stored in an account with a deterministic address. A client - usually a web wallet - would then simulate the same deterministic calculation, using its knowledge of the VAA's digest - and retrieve the VAA from its associated Solana account. The client then posts it to the target chain, with the fee paid by the user. On Solana, no client-side submission is necessary - the VAA is executed immediately when posted for data availability. + +However, while this design worked great for v1 which was bridging only two chains, it's not optimal for v2: + +- It adds an unnecessary point of failure. A Solana outage would also prevent token transfers between unrelated chains (it's called mainnet-beta for a reason!). Messages would also incur unnecessary extra latency waiting for Solana to include the transaction. At the time of writing, this is exacerbated by high mainnet-beta skip rate which causes a significant percentage of transactions to fail to be included within a reasonable interval. + +- It puts extra strain on Solana's network. In particular, for messages _originating_ from Solana, it would cause write amplification. + +- The race mechanism would be too expensive at scale and hard to incentivize, since we cannot use preflight and nodes would pay for failed messages, likely unequally. A leader selection mechanism would have to be implemented. + +- Guardian nodes are required to maintain sufficient SOL balance in their wallets and need to be compensated and incentivized to actually submit transactions. + +- Reproducing the deterministic Solana account address is complex to do client-side. + +Our data availability requirements do not actually require messages to be posted on a finalized chain - we simply used one for convenience. Anything that reliably shuttles bytes from the guardian p2p network to the client will do the trick, and we can replace on-chain storage by a better-suited custom mechanism. + +## Goals + +- The mechanism must enable any client - native, app or web - to wait for and retrieve the signed VAA message for a specific message, identified by its unique (chain, emitter, sequence) identifier. + +- Signed VAAs must be available to clients at least until the message was posted on the target chain. Ideally, our design would enable an optional full archive of signed VAAs to be maintained. + +- The design's performance characteristics must be sufficient to persist and retrieve all signed VAAs from all networks within at most 100-200ms per message, with all networks publishing messages concurrently at full capacity. + +## Non-Goals + +- The design facilitates a relayer mechanism where an incentivized third party would submit the transaction to the target chain, but it does not specify how such a mechanism would be implemented. + +- Designing an incentivization scheme to compel guardians to run a public API. We assume an effective incentive is provided for guardian nodes to run high-quality API endpoints. + +- Discovery of public API endpoints by clients. We assume that a well-known set of load balanced API frontends will be documented and hardcoded by client applications. + +- Design of supporting infrastructure (scaling, caching, load balancing, ...) + +- Resolving the fee payer issue - users need existing tokens on the target chain in order to pay fees for the client-side message submission transaction. This is a problem for users who want to use a token bridge to get such native tokens in the first place. + +- Denial of service prevention for the underlying gossip network. + +- Optimization for very large state (dozens to hundreds of GB). + +- State synchronization that would allow offline nodes to "catch up" on signed VAA broadcasts they missed due to downtime. Operators can instead run multiple nodes for high availability to ensure they do not miss broadcasts. + +## Overview + +Instead of submitting signed VAAs to Solana, guardians instead broadcast them on the gossip network and persist the signed VAAs locally. + +Guardians that failed to observe the message (and therefore cannot reconstruct the VAA) will verify the broadcasted signed VAA and persist it as if they had observed it. + +A public API endpoint is added to guardiand, exposing an API which allows clients to retrieve the signed VAA for any (chain, emitter, sequence) tuple. Guardians can use this API to serve a public, load-balanced public service for web wallets and other clients to use. + +Clients will rely on public API endpoints operated by different guardian node operators or third party service providers when polling for signed VAA messsages. + +## Detailed Design + +The current guardiand implementation never broadcasts the full, signed VAA on the gossip network - only signatures. Guardians then use their own message observations and the aggregated set of signatures to assemble a valid signed VAA locally. Once more than 2/3 of signatures are present, the VAA is valid and can be submitted to the target chain. Nodes that haven't observed the message due to issues with the connected chain nodes are unable to construct a full VAA and will eventually drop the aggregated set of signatures. + +Depending on the order of receipt and network topology, the aggregated set of signatures seen when the 2/3+ threshold is crossed is different from each node, but each node's VAA digest will be identical. + +In v1, a node would submit the VAA directly to Solana, with complex logic for fault tolerance and retries. The first signed VAA would "win" a race and be persisted on-chain as the canonical signed VAA for this message. + +Instead, each node will now locally persist the full signed VAA and broadcast it to the gossip network, where it can be received both by guardian nodes and unprivileged nodes (like future relayer services) that joined the gossip network. + +Locally persisted state is crucial to maintain data availability across the network - it is used to serve API queries (if enabled) and rebroadcast signed VAAs to other guardians that missed them. + +We can't rely on gossip to provide atomic or reliable broadcast - messages may be lost, or nodes may be down. We need to assume that nodes can and will lose all of their local state, and be down for maintenance, including nodes used to serve a public API. Clients relying on the API therefore have to rely on multiple nodes to provide fault tolerance. Typically, clients would implement this by rotating through a set of known API endpoints operated by different service providers, alternating or randomizing them while polling for VAA completion. + +Each individual API endpoint may in turn be backed by multiple nodes operated behind a load balancer operated by each API endpoint's service provider. + +To facilitate this, a new "non-voting" mode will be added to guardiand to allow operators to run read-only nodes that listen to the gossip network and locally persist any signed VAA they receive. + +We use the (chain, emitter, sequence) tuple as global identifier. The digest is not suitable as a global identifier, since it is not known at message publication time. Instead, all contracts make the sequence number available to the caller when publishing a message, which the caller then surfaces to the client. Chain and emitter address are static. + +This design deprecates existing usage of the digest as primary global identifier for messages in log messages and other user- and operator-facing interfaces, aiding troubleshooting. This a presentation layer change only - the digest continues to be used for replay protection and signature verification. + +The guardiand submission state machine will be refactored to maintain aggregation state for observed VAAs in the local key-value store, rather than in-memory. This has the nice side effect of removing the timeout for observed-but-incomplete VAAs, allowing them to be completed asynchronously when missing nodes are brought back up and use chain replay to catch up on missed blocks. + +### Contracts + +No changes are required to smart contracts. + +## Alternatives considered + +### Single-provider redundancy + +Instead of client side redundancy, we could instead make it an API provider's responsibility to maintain a complete record of VAAs by running multiple nodes listening to the gossip network. + +Nodes could do idempotent writes to a single shared K/V store (like Bigtable or Redis), doing fallthrough API requests against other nodes in the cluster, or retry on the LB level. + +However, we decided against this approach: + +- It creates a false sense of security by allowing clients to rely on a single API service provider, reducing the network's overall level of fault tolerance. + +- We want to mitigate gossip message propagation issues or operational mistakes that could affect many nodes. + +- For decentralization reasons, it should be possible to serve a fully-functional public API using a single node without requiring complex external dependencies or multiple nodes in separate failure domains. + +- The necessary client-side logic is trivial to implement - clients already need to poll for VAAs. + +### Direct P2P connectivity + +libp2p supports a WebRTC transport, which would - in theory - allow web wallets to directly join the guardian gossip network. However, we decided not to pursue this route: + +- libp2p is very complex and it's not clear how well such an approach would scale. Debugging any scalability (or other) issue likely requires in-depth libp2p debugging, which we have no experience with. In comparison, the challenges that come with a traditional RPC scale-out approach are much better understood. + +- The only available reference implementation is written in Node.js, which is [incompatible](https://github.com/libp2p/js-libp2p/issues/287) with our libp2p QUIC transport. We would either have to join the main IPFS gossip network to take advantage of existing WebRTC-capable nodes, which would be a performance and security concern, or add support for a compatible transport to guardiand and run separate bridge nodes. + +- Clients would have to publish messages to the gossip network, and a complex spam prevention mechanism would be needed. + +Directly connecting to the gossip network remains a possible design for future for non-web clients like relayers that can speak the native libp2p QUIC protocol. Even a future implementation using the WebRTC transport remains a plausible avenue for future development. + +## Caveats + +### Stochastic failure + +Unless every guardian node on the network exposes an API endpoint, it is theoretically possible that 2/3+ nodes observe and sign a message, but all the nodes belonging to public API endpoints missed it. + +The risk of this is insignificant with libp2p pubsub in a decentralized network, and manual recovery would be possible (any of the nodes in the 2/3+ set could retrieve the VAA and manually deliver it). + +### Decentralization concerns + +Using Solana for data availability comes with a well-established ecosystem of RPC service providers. This design instead requires facilitating a new API provider ecosystem. If too few providers exist, or the ecosystem converges on a few large monopolists, it could cause unwanted centralization in an otherwise trustless, decentralized system. + +We believe that our proposal instead improves decentralization: Solana RPC nodes are expensive and complex to operate due to Solana's very high throughput and the large amount of state it needs to handle. In contrast, the Wormhole data availability problem is trivial - all we need is eventual at-least-once delivery of small key-value pairs. Serving and scaling out Wormhole RPC nodes is a much easier task than running Solana validators, which means that a larger number of parties will be able to provide them. The design makes it very easy to run a public API using autoscaling cloud services backed by any distributed key-value store. + +## Security Considerations + +This proposal affects only data availability of data that was already validated and signed. If the in-band data availability mechanism fails, out-of-band methods can be used to ensure data availability (like manually fetching and posting signed VAAs). + +### Denial of service + +API endpoints may be subject to denial of service attacks. If an attacker manages to take down all public API endpoints, clients would be unable to retrieve signed messages. + +VAAs would still be persisted locally during such an attack and can be requested once availability is restored. + +We believe this risk is easily mitigated - protecting web APIs from denial of service attacks is a well-understood problem, with a robust ecosystem of both technological solutions and service providers. + +(robustness of libp2p pubsub itself against flooding by non-guardian nodes is an orthogonal concern tracked in https://github.com/wormhole-foundation/wormhole/issues/22 as well as the [official libp2p docs](https://docs.libp2p.io/concepts/security-considerations/)) diff --git a/docs/whips/0006_nft_bridge.md b/docs/whips/0006_nft_bridge.md new file mode 100644 index 00000000..8df953de --- /dev/null +++ b/docs/whips/0006_nft_bridge.md @@ -0,0 +1,159 @@ +# NFT bridge App + +[TOC] + +## Objective + +To use the Wormhole message passing protocol to transfer NFTs between different connected chains. + +## Background + +NFTs are a new asset class that has grown in popularity recently. It especially attracts new users and companies to +crypto. NFTs, just like traditional tokens, are minted on a single blockchain and cannot be transferred to other chains. +Howevre as more chains introduce NFT standards and marketplaces there is demand for ways to transfer NFTS across chains +to access these markets and collect them in a single wallet. + +## Goals + +We want to implement a generalized NFT bridge using the Wormhole message passing protocol that is able to bridge any +standards-compliant NFT between chains, creating unique wrapped representations on each connected chain on demand. + +* Allow transfer of standards-compliant NFTs between chains. +* Use a universal NFT representation that is compatible with most VM data types. + +## Non-Goals + +* Support EIP1155 +* Manage / Transfer chain-specific NFT metadata that isn't broadly applicable to all chains. +* Automatically relay NFT transfer messages to the target chain. + +## Overview + +On each chain of the NFT bridge network there will be a NFT bridge endpoint program. + +These programs will manage authorization of payloads (emitter filtering), wrapped representations of foreign chain +NFTs ("Wrapped NFTs") and custody locked NFTs. + +We aim to support: + +- EIP721 with token_uri extension: Ethereum, BSC +- Metaplex SPL Meta: Solana +- CW721 with token_uri extension: Terra + +## Detailed Design + +For outbound transfers, the contracts will have a lock method that either locks up a native NFT and produces a +respective Transfer message that is posted to Wormhole, or burns a wrapped NFT and produces/posts said message. + +For inbound transfers they can consume, verify and process Wormhole messages containing a NFT bridge payload. + +There will be three different payloads: + +* Transfer - Will trigger the release of locked NFTs or minting of wrapped NFTs. + +Identical to the NFT bridge: + +* RegisterChain - Register the NFT bridge contract (emitter address) for a foreign chain. +* UpgradeContract - Upgrade the contract. + +In order to transfer an NFT to another chain, a user needs to call the transfer method of the bridge contract with the +recipient details. The contract will either hold the NFTs in a custody account (in case it is a native NFT) or burn +wrapped NFTs. Wrapped NFTs can be burned because they can be freely minted once they are transferred back. After the +lockup the contract will post a Transfer payload message to Wormhole. Once the message has been signed by the guardians, +it can be posted to the target chain of the transfer. The target chain will then either release the native NFT from +custody or mint a wrapped NFT depending on whether it's a native NFT there. The program will keep track of consumed +message digests for replay prevention. + +Since the method for posting a VAA to the NFT bridge is authorized by the message signature itself, anyone can post any +message. + +Since every NFT has unique metadata the Transfer messages contain all metadata, a transfer (even the first on per NFT) +only requires a single Wormhole message to be passed compared to the Token Bridge. On the first transfer action of an +NFT (address / symbol / name) a wrapped asset (i.e. master edition or new contract) is created. When the wrapped asset ( +contract) is already initialized or was just initialized, the (new) token_id and metadata URI are registered. + +### API / database schema + +Proposed bridge interface: + +transfer(address token, uint256 token_id, uint16 recipient_chain, bytes32 recipient) - Initiate a Transfer + +completeTransfer(Message transfer) - Execute a Transfer message + +registerChain(Message registerChain) - Execute a RegisterChain governance message + +upgrade(Message upgrade) - Execute a UpgradeContract governance message + +--- +Payloads: + +Transfer: + +``` +PayloadID uint8 = 1 +// Address of the NFT. Left-zero-padded if shorter than 32 bytes +NFTAddress [32]uint8 +// Chain ID of the NFT +NFTChain uint16 +// Symbol of the NFT +Symbol [32]uint8 +// Name of the NFT +Name [32]uint8 +// ID of the token (big-endian uint256) +TokenID [32]uint8 +// URI of the NFT. Valid utf8 string, maximum 200 bytes. +URILength u8 +URI [n]uint8 +// Address of the recipient. Left-zero-padded if shorter than 32 bytes +To [32]uint8 +// Chain ID of the recipient +ToChain uint16 +``` + +RegisterChain: + +``` +PayloadID uint8 = 2 +// Chain ID +ChainID uint16 +// Emitter address. Left-zero-padded if shorter than 32 bytes +EmitterAddress [32]uint8 +``` + +UpgradeContract: + +``` +PayloadID uint8 = 3 +// Address of the new contract +NewContract [32]uint8 +``` + +## Caveats + +There is no guarantee for completion of transfers. If a user initiates a transfer and doesn't call completeTransfer on +the target chain, a transfer might not be completed. In case a guardian set change happens in-between and the original +signer guardian set expires, the transfer will be stuck indefinitely. + +Since there is no way for a NFT bridge endpoint to know which other chain already has wrapped assets set up for the +native asset on its chain, there may be transfers initiated for assets that don't have wrapped assets set up yet on the +target chain. However, the transfer will become executable once the wrapped asset is set up (which can be done any time) +. + +The name and symbol fields of the Transfer payload are not guaranteed to be +valid UTF8 strings. Implementations might truncate longer strings at the 32 byte +mark, which may result in invalid UTF8 bytes at the end. Thus, any client +whishing to present these as strings must validate them first, potentially +dropping the garbage at the end. + +Currently Solana only supports u64 token ids which is incompatible with Ethereum which specifically mentions the use of +UUIDs as token ids (utilizing all bytes of the uint256). There will either need to be a mechanism to translate ids i.e. +a map of `[32]u8 -> incrementing_u64` (in the expectation there will never be more than MaxU64 editions) or Solana needs +to change their NFT contract. + +Terra CW721 contracts support arbitrary strings as token IDs. In order to fit +them into 32 bytes, we store their keccak256 hash instead. This means that when +transferring a terra-native NFT through the wormhole, the ID of the output token +will be the original token's hash. However, wrapped assets on terra will retain +their original token ids, simply stringified into a decimal number. Then, +when transferring them back through the wormhole, we can guarantee that these +ids will parse as a uint256. diff --git a/docs/whips/0007_governor.md b/docs/whips/0007_governor.md new file mode 100644 index 00000000..c4b4b6b6 --- /dev/null +++ b/docs/whips/0007_governor.md @@ -0,0 +1,75 @@ +# Governor + +[TOC] + +## Objective + +Provide an optional security layer that enables Guardians to limit the amount of notional value that can be transferred out of a given chain within a sliding time period, with the aim of protecting against external risk such as smart contract exploits or runtime vulnerabilities. + +## Background + +Bridge security is incredibly high stakes — beyond core trust assumptions and high code quality, it is important to have defense in depth to minimize the potential for user harm. Under the assumption of smart contract bugs, the Governor is designed to be a passive security check that individual Guardians can implement to rate limit the notional value of assets that can be transferred out of a given chain to ensure the integrity of the value stored within a token bridge. + +## Goals + +- Implement an optional security check for Guardians to incorporate in a message verification process based on notional value processed by chain +- Limit the notional movement of value out of a given chain over a period of time + +## Non-Goals + +- Set a blanket rate limiting on all supported chains for all tokens +- Prevent any single "bad actor" from blocking other value transfer by intentionally exceeding the transfer limit for the given time period + +## Overview + +Each individual Guardian within the Guardian network should employ a set of strategies to verify the validity of a VAA. The Governor is designed to check VAAs that transfer tokens by enforcing limits on the notional value that can be transferred from a given chain over a specific period of time. + +The current implementation works on two classes of transaction (large and small) and current configuration can be found [here](https://github.com/wormhole-foundation/wormhole/blob/main/node/pkg/governor/mainnet_chains.go): + +- **Large Transactions** + - A transaction is large if it is greater than or equal to the `bigTransactionSize` for a given origin chain. + - All large transactions will have a mandatory 24-hour finality delay and will have no affect on the `dailyLimit`. +- **Small Transactions** + - A transaction is small if it is less than the `bigTransactionSize` for a given origin chain. + - All small transactions will have no additional finality delay up to the `dailyLimit` defined within a 24hr sliding window. + - If a small transaction exceeds the `dailyLimit`it will be delayed until it either + - fits inside the `dailyLimit` and will be counted toward the `dailyLimit` + - has been delayed for 24-hours and will have no affect on the `dailyLimit`. + +## Detailed Design + +The Governor is implemented as an additional package that defines (1) a `ChainGovernor` object, (2) `mainnet_tokens.go`, a single map of tokens that will be monitored, and (3) `mainnet_chains.go`, a map of chains governed by the chain governor. + +The `mainnet_tokens.go` maps a list of tokens with the maximum price between a hard-coded token floor price and the latest price read from CoinGecko. + +If a node level config parameter is enabled to indicate that the chain governor is enabled, all VAAs will be passed through the `ChainGovernor` to perform a series of additional checks to indicate whether the message can be published or if it should not and be dropped by the processor. + +The checks performed include: + +1. Is the source chain of the message one that is listed within `mainnet_chains.go`? +2. Is the message sent from a goverened emitter? +3. Is the message a known type that transfers value? +4. Is the token transferred listed within `mainnet_tokens.go`? +5. Is the transaction a “large” transaction (ie. greater than or equal to `bigTransactionSize` for this chain)? +6. Is the transaction a “small” transaction (ie. less than `bigTransactionSize` for this chain)? + +The above checks will produce 3 possible scenarios: + +- **Non-Governed Message**: If a message does not pass checks (1-4), `ChainGovernor` will indicate that the message can be published. +- **Governed Message (Large)**: If a message is “large”, `ChainGovernor` will wait for 24hrs before signing the VAA and place the message in a queue. +- **Governed Message (Small)**: If a message is “small”, `ChainGovernor` will determine if it fits inside the `dailyLimit` for this chain. If it does fit, it will be signed immediately. If it does not fit, it will wait in the queue until it does fit. If it does not fit in 24hrs, it will be released from the queue. + +While messages are enqueued, any Guardian has a window of opportunity to determine if a message is fraudulent using their own processes for fraud detection. If Guardians determine a message is fraudulent, they can delete the message from the queue from their own independently managed queue. If a super minority of Guardians (7 of 19) delete a message from their queues, this fraudulent message is effectively censored as it can no longer reach a super-majority quorum. + +In this design, there are three mechanisms for enqueued messages to be published: + +- A quorum (13/19) of Guardians can manually override the Governor and release any pending messages. + - _Messages released through this mechanism WOULD NOT be added to the list of the processed transactions to avoid impacting the daily notional limit as maintained by the sliding window._ +- Guardians will periodically check if a message can be posted without exceeding the daily notional limit as the sliding window and notional value of the transactions change. + - _Messages released through this mechanism WOULD be added to the list of processed transactions and thus be counted toward the daily notional limit._ +- Messages will be automatically released after a maximum time limit (this time limit can be adjusted through governance and is currently set to 24 hours). + - _Messages released through this mechanism WOULD NOT be added to the list of the processed transactions to avoid impacting the daily notional limit as maintained by the sliding window._ + +## Potential Improvements + +Right now, adding more governed emitters requires modifying guardian code. In the future, it would be ideal to be able to dynamically add new contracts for guardian nodes to observe. diff --git a/docs/whips/0008_batch_messaging.md b/docs/whips/0008_batch_messaging.md new file mode 100644 index 00000000..8a2364be --- /dev/null +++ b/docs/whips/0008_batch_messaging.md @@ -0,0 +1,156 @@ +# Batch VAAs (EVM) + +[TOC] + +## Objective + +Add batch VAAs to Wormhole to allow for efficient verification of multiple messages, as well as allowing for better patterns for xDapps to compose with each other. + +## Background + +Currently, composing between different cross-chain applications is often leading to design patterns where developers are nesting all actions into a common VAA that contains all instructions to save gas and ensure atomic execution. This pattern is sub-optimal as it often requires multiple composing xDapps to integrate each other and extend their functionality for new use-cases and delivery methods. This is undesirable as it adds more code-complexity over time and slows down integration efforts. + +## Goals + +Extend Wormhole with the core-primitives needed to build better composability patterns by leveraging batching. + +- Individual VAAs included in a batch should stay backwards compatible with existing smart contract integrations. +- Batch VAAs should be usable in a gas-efficient manner. +- Allow for cheaper verification of individual VAAs included in a batch. + +## Non-Goals + +This design document focuses only on the extension of the current implementation of Wormhole’s generic message passing ([0001_generic_message_passing.md](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0001_generic_message_passing.md)) and does not attempt to solve the following problems, leaving them for future design iterations: + +- Replacing VAAv1s with batch VAAs (VAAv2) containing only a single observation. +- Ensuring backwards compatibility of batch VAAs (VAAv2) with only a single observation using the existing Wormhole APIs. +- Verifying batch VAAs with a subset of the original observations. +- The specifics of implementing xDapps leveraging batch VAAs, other than ensuring the right APIs are provided. + +## Overview + +For now, all Wormhole messages that are emitted during a transaction will continue to receive individual signatures (VAAv1). This is mainly to ensure backwards compatibility and might be deprecated in the future. + +Guardians will start producing batch-signatures for messages emitted within the same transaction and that share the same nonce. However, messages with a nonce of zero will not receive batch-signatures, as this is a way of opting out of including messages in a batch VAA. + +The number of messages within a batch is constrained by the the maximum `uint8` value of 255, due to the [VAAv2 Payload Encoding format](#payloads-encoded-messages). If a transaction produces more than 255 messages with the same nonce a batch-signature will not be produced because it would not fit within the binary encoding understood by `parseAndVerifyBatchVM`, and therefore could not be successfully verified on-chain. Transactions may include messages to produce multiple batch-signatures, which would be independent of each other verified individually. + +We will add support for two new VAA payload types to the Wormhole core contract to allow handling of these: + +- The VAAv2 payload that holds the batch-signatures, an array of the signed hashes and an array of observations (the [Structs](#structs) section of this paper defines `observation`). This VAAv2 payload can be verified using a new Wormhole core contract endpoint `verifyBatchVM`. This payload will also be produced for individual messages with a nonce greater than zero to offer integrators flexibility when deciding which Wormhole core endpoint to verify messages with. +- The VAAv3 payload, which is a “headless” payload that only carries an observation. This payload can only be verified when its hash is cached by the Wormhole core contract during VAAv2 signature verification. This payload type is created when a VAAv2 is parsed using the Wormhole core endpoint `parseBatchVM` by prepending the version type to the observation bytes. Although the payload format for VAAv3 is new, it will be parsed and verified using the existing Wormhole core endpoints and parsed into the existing `VM` struct (`signatures[]` and `guardianSetIndex` will be null) to ensure backwards compatibility. + +## Detailed Design + +### VAAv2 + +To create a VAAv2 payload (which is eventually parsed into the `VM2` struct) an xDapp will invoke the `publishMessage` method at least one time with a nonce greater than zero. The guardian will then produce a VAAv2 payload by grouping messages with the same `nonce` (in the same transaction) and create a batch-signature by signing the payload version (`uint8(2)` for batches) and hash of all hashes of the observations: + +`hash(version, hash(hash(Observation1), hash(Observation2), ...))` + +Once the batch is signed by the guardian, the VAAv2 can be parsed and verified by calling the new Wormhole core endpoint `parseAndVerifyBatchVM`. This method parses the VAAv2 into the `VM2` struct by calling `parseBatchVM`, calls `verifyBatchVM` to verify the batch-signatures, and stores the hash of each observation in a cache when specified by the caller (for reasons explained in the [VAAv3](#vaav3) section of this detailed design). `verifyBatchVM` also independently computes the hash of each observation and validates that each hash is stored in the `hashes` array, which is included in the VAAv2 payload. The structure of the VAAv2 payload can be found in the [Payloads](#payloads-encoded-messages) section of this design. + +### VAAv3 + +When a VAAv2 payload is parsed into a `VM2` struct, each observation is stored as bytes in the `observations` byte array. The `uint8(3)` version type is prepended to the bytes to specify that they are considered a VAAv3 payload. Each VAAv3 payload can be parsed into the existing `VM` struct with `parseVM` to ensure backwards compatibility with existing smart contract integrations. Since VAAv3 payloads are considered “headless” and do not contain signatures, the `Signatures[]` and `guardianSetIndex` fields are left as null in the `VM` struct. + +A parsed VAAv3 payload can then be verified by calling the existing method `verifyVM`. This method will check that the hash of the VAAv3 payload (hash of the observation) is stored in the `verifiedHashCache` and bypass signature verification (allowing for cheap verification of individual messages). The VAAv3 payload hash will only be stored in the `verifiedHashCache` if the caller sets the `cache` argument to `true` when verifying the associated VAAv2 payload with `verifyBatchVM`. + +At the end of a batch execution, the handler contract should call `clearBatchCache` which will clear the `verifiedHashCache` of provided hashes and reduce the gas costs associated with storing the hashes in the Wormhole contract’s state. A parsed VAAv3 payload will no longer be considered a verified message once its hash is removed from the `verifiedHashCache`. + +### API + +```solidity +function parseAndVerifyBatchVM(bytes calldata encodedVM2, bool cache) +function verifyBatchVM(Structs.VM2 memory vm2, bool cache) +function parseBatchVM(bytes memory encodedVM2) +function clearBatchCache(bytes32[] memory hashesToClear) +``` + +### Structs + +```solidity +struct Header { + uint32 guardianSetIndex; + Signature[] signatures; + bytes32 hash; +} + +// This struct exists already, but now has an additional version type 3. +struct VM { + uint8 version; // Version = 1 or 3 + // The following fields constitute an `observation`. For compatibility + // reasons we keep the representation inlined. + uint32 timestamp; + uint32 nonce; + uint16 emitterChainId; + bytes32 emitterAddress; + uint64 sequence; + uint8 consistencyLevel; + bytes payload; + // End of observation + + // Inlined Header + uint32 guardianSetIndex; + Signature[] signatures; + + // Hash of the observation + bytes32 hash; +} + +struct VM2 { + uint8 version; // Version = 2 + + // Inlined header + uint32 guardianSetIndex; + Signature[] signatures; + + // Array of observation hashes + bytes32[] hashes; + + // Computed Batch Hash - `hash(version, hash(hash(Observation1), hash(Observation2), ...))` + bytes32 hash; + + // Array of observation bytes with prepended version 3 + bytes[] observations; +} +``` + +### Payloads (Encoded Messages) + +VAAv2: + +```solidity +// Version uint8 = 2; +uint8 version; +// Guardian set index +uint32 guardianSetIndex; +// Number of signatures +uint8 signersLen; +// Signatures: 66 bytes per signature +Signature[] signatures; +// Number of hashes +uint8 hashesLen; +// Array of observation hashes +bytes32[] hashes; +// Number of observations, should be equal to hashesLen +uint8 observationsLen; + +// Repeated for observationLen times, bytes[] observation + // Index of the observation + uint8 index; + // Number of bytes in the observation + uint32 observationBytesLen; + // Encoded observation, see the Structs section + // for details on the observation structure. + bytes observation; +``` + +VAAv3: + +```solidity +// Version uint8 = 3; +uint8 version; +// Observation bytes +bytes observation; +``` diff --git a/docs/whips/0009_guardian_key.md b/docs/whips/0009_guardian_key.md new file mode 100644 index 00000000..a217dc87 --- /dev/null +++ b/docs/whips/0009_guardian_key.md @@ -0,0 +1,37 @@ +# Guardian Key Usage + +## Objective +* Describe how guardian keys are used and how message confusion is avoided. + + +## Background +Message confusion could occur when a Guardian signs a message and an attacker replays that message elsewhere where it is interpreted as a different message type, which could lead to unintended behavior. + + +## Overview +The Guardian Key is used to: +1. Sign gossip messages + 1. heartbeat + 2. governor config and governor status + 3. observation request +2. Sign Observations + 1. Version 1 VAAs + 2. Version 2 VAAs, i.e. Batch VAAs. + +## Detailed Design + +Signing of gossip messages: +1. Prepend the message type prefix to the payload +2. Compute Keccak256Hash of the payload. +3. Compute ethcrypto.Sign() + +Signing of Observations: +* v1 VAA: `double-Keccak256(observation)`. +* v2 (batchVAA): `double-Keccak256(version | Keccak256(hash1 | hash2 | ... | hash_n))`, where `|` stands for concatenation. + +Rationale +* Gossip messages cannot be confused with other gossip messages because the message type prefix is prepended. +* Gossip messages cannot be confused with observations because observations utilize a double-Keccak256 and the payload is enforced to be `>=34` bytes. +* v2 VAAs cannot be confused as v1 VAAs because their payload when parsed as a v1 VAA is only 33 bytes, which does not constitute a valid observation. +* v1 VAAs cannot be confused as v2 VAAs because observations are longer than 33 bytes and hence do not constitute a valid v2 VAA body. + diff --git a/docs/whips/0010_integrity_checkers.md b/docs/whips/0010_integrity_checkers.md new file mode 100644 index 00000000..b26d73b0 --- /dev/null +++ b/docs/whips/0010_integrity_checkers.md @@ -0,0 +1,45 @@ +# Integrity Checkers + +Wormchain Integrity Checkers are smart contracts deployed on Wormchain. They allow xApp developers to validate the integrity of cross-chain messages based on a globally synchronized state. This document describes the motivation, goals, and technical design. + +## Objectives + +- Allow xApps to implement safety checks on Wormchain that have access to the global, synchronized, state of the xApp. +- Integrity Checkers on Wormchain should be trustless. This means they can block but not create or modify messages. + +## Background + +xApps generally have one of two architectures: + +1. Hub-and-spoke: A core contract is deployed on a main chain and has the global view of the cross-chain application. On each connected chain, there is a smaller contract, allowing for interaction with the core contract. This architecture is simple, but the main disadvantages are increased latency and increased cost when interacting between two chains that are not the hub. Example: ICCO. +2. Distributed: Each contract is the peer of multiple contracts on other chains and directly sends/receives messages between them. The main advantages are no reliance on a single chain, lower latency, and transaction fees can be lower. The main downside is the difficulty of synchronizing state between all chains. Example: Portal Token Bridge. + +These two architectures drastically differ in their trust model: In the hub-and-spoke model, the contract on the hub chain has access to the global state of the xApp and can enforce global invariants. For example, a token bridge implemented in the hub-and-spoke architecture could enforce that a chain cannot send more wrapped tokens than have been deposited. + +Conversely, the lack of synchronized state in the distributed xApp model often leads to the xApp trusting all connected chains. These chains may have different trust models and security properties, which could lead to the xApp relying on the weakest link. For example, a token bridge implemented in the decentralized architecture could be drained if a single chain has a fault. + +## Goals + +### Primary goals + +- Motivate the concept of integrity checkers + +### Out of scope (for now) + +- Registration of Integrity Checkers: For now, Wormchain will be restricted to trusted Integrity Checkers. +- Gas cost: For now, executing the checkers on Wormchain will be free. + +## Overview + +After making an observation, a Guardian checks if there are integrity checkers configured for the emitter. If there are, it submits a pre-observation to the integrity-checker smart contract on Wormchain. It then saves the pre-observation to a local database. + +After its conditions are met, an integrity-checker approves the wormhole message it deems to be valid. + +The Guardian picks up the messages approved from the integrity-checkers on Wormchain when they correspond to an observation the Guardian has made itself. It signs and broadcasts the signature to the Guardian peer to peer network. Thereby, an integrity-checker is unable to modify or inject messages and can only block messages. + +## Terminology + +* `Pre-Observation` - used to designate an observation being submitted to the integrity checker contract by a Wormhole Guardian. They are similar to observations, i.e. a Wormhole message signed by a single guardian, but can be distinguished by their prefix and signature format. They follow the same signature format as signed gossip messages described in [guardian key usage](0009_guardian_key.md) with a unique signature prefix. Signed pre-observations therefore cannot be used like signed observations to create a VAA. +* `Batching` - pre-observations accumulate over a configurable time period and are batch submitted to Wormchain. This both saves on gas costs and results in less computational overhead. +* `Persistence` - the Guardian persists the status of pre-observations to the local database. +* `Retry` - the Guardian periodically watches the Wormchain state and ensures that all local pre-observations have been submitted correctly. In case of an error, it retries the submission. diff --git a/docs/whips/0011_accountant.md b/docs/whips/0011_accountant.md new file mode 100644 index 00000000..9170ec2f --- /dev/null +++ b/docs/whips/0011_accountant.md @@ -0,0 +1,190 @@ +# Global Accountant + +This document describes the motivation, goals, and technical design of the "Global Accountant". + +The Global Accountant is a safety feature for registered token bridges. It is implemented as an [Integrity Checker](0010_integrity_checkers.md) on Wormchain. + +## Objectives + +Tightly limit the impact an exploit in a connected chain can have on a registered token bridge. Ensure the number of wrapped tokens capable of being moved out of a chain never exceed the number sent to that chain, even if that chain was compromised. + +## Background + +Token Bridges built on Wormhole (e.g. [Portal](0003_token_bridge.md)) allow transferring tokens between any of the various blockchains connected to the bridge. + +A core feature of the token bridge is that xAssets are fungible across all chains. ETH sent from Ethereum to Solana to Polygon is the same as the ETH sent from Ethereum to Aptos to Polygon. Transfers work by locking native assets (or burning wrapped assets) on the source chain and emitting a Wormhole message that allows the minting of wrapped assets (or unlocking of native assets) on the destination chain. + +It is unfortunately not practical to synchronize the state of the token bridge across all chains: If a transfer of Portal-wrapped ETH happens from Solana to Polygon, the token bridge contract on Ethereum is not aware of that transfer and therefore doesn't know where the wrapped assets are currently located. + +If any connected chain gets compromised, e.g. through a bug in the rpc nodes, this could cause arbitrary minting of “unbacked” wrapped assets, i.e. wrapped assets that are not backed by native assets. Those unbacked wrapped assets could then be transferred to the native chain. Because wrapped assets are fungible, the smart contract on the native chain cannot distinguish those unbacked wrapped assets and would proceed with unlocking the native assets, effectively draining the bridge. + +The [Governor](0007_governor.md) limits the maximal impact of such an attack, but cannot fully prevent it. + +The core of the issue is the differing trust assumptions and security program maturity of various blockchains. By design, the Token Bridge is subject to the weakest link. A holder of a wrapped asset should only be subject to security risks from the asset's native chain as well as the chain where the wrapped asset lives. They should not be subject to security risks from other chains connected to the bridge. + +## Goals + +### Primary Goals + +- Localize cross-chain risk by ensuring no more wrapped tokens can be moved out of a chain than have been sent to that chain, even if that chain was compromised. + +### Nice-to-haves + +- Support any token bridge, not just Portal. +- Provide ground-truth statistics for locked and minted assets per chain, transfer activity, etc. +- Minimize smart contract changes. Ideally, we won't need to change the Token Bridge smart contracts at all. + +### Non-Goals + +- Protect against any exploit that doesn't go cross-chain e.g. a bug in a smart contract allowing minting of unbacked wrapped assets, the attacker might swap these wrapped assets into other valuable assets through other protocols or through exchanges without going through Wormhole. + +## Overview + +The `global-accountant` contract on wormchain acts as an [Integrity Checker](0010_integrity_checkers.md). Guardians submit [pre-observations](0010_integrity_checker.md#pre-observations) to it and only finalize their observations if the `global-accountant` gives the go-ahead. + +`global-accountant`keeps track of the tokens locked and minted on each connected blockchain. + +Once a Token Bridge message has reached a quorum of pre-observations (i.e. 13 guardians have submitted a pre-observation), it attempts to apply the transfer to the account balances for each chain. If the transfer doesn't result in a negative account balance, it is allowed to proceed. + +## Detailed Design + +### Accountant Smart Contract + +#### Query methods for signature verification + +The global-accountant is a cosmwasm-based smart contract rather than a built-in native cosmos-sdk module. It needs to expose query methods from the native wormhole module to cosmwasm (like getting the members of a guardian set). Luckily, extending the contract interface is [fairly straightforward](https://docs.cosmwasm.com/docs/1.0/integration#extending-the-contract-interface). The `x/wormhole` native module will expose the following query methods: + +```rust +#[cw_serde] +#[derive(QueryResponses)] +pub enum WormholeQuery { + /// Verifies that `vaa` has been signed by a quorum of guardians from valid guardian set. + #[returns(Empty)] + VerifyVaa { + vaa: Binary, + }, + + /// Verifies that `data` has been signed by a guardian from `guardian_set_index` using a correct `prefix`. + #[returns(Empty)] + VerifyMessageSignature { + prefix: Binary, + data: Binary, + guardian_set_index: u32, + signature: Signature, + }, + + /// Returns the number of signatures necessary for quorum for the given guardian set index. + #[returns(u32)] + CalculateQuorum { guardian_set_index: u32 }, +} +``` + +#### Submitting observations to wormchain + +When a guardian observes a token transfer message from the registered tokenbridge on a particular chain, it submits the observation directly to the accountant. The contract keeps track of pending token transfers and emits an event once it has received a quorum of observations. If a quorum of guardians submit their observations within a single tendermint block then the token transfer only needs to wait for one round of tendermint consensus and one round of guardian consensus. + +When a guardian submits a transfer (observation) to the contract, the contract will perform several checks: + +- Check that the signature is valid and from a guardian in the current guardian set. +- If the transfer does not yet have a quorum of signatures, return a `Pending` status and mark the transfer as having a signature from the guardian. +- If a transfer with the same `(emitter_chain, emitter_address, sequence)` tuple has already been committed, then check that the digest of the transfer matches the digest of the committed transfer. If they match return a `Committed` status; otherwise return an `Error` status and emit an `Error` event. +- If the transfer has a quorum of signatures, parse the tokenbridge payload and commit the transfer. +- If committing the transfer fails (for example, if one of the accounts does not have a sufficient balance), return an `Error` status and emit an `Error` event. Otherwise return a `Committed` status and emit a `Committed` event. + +If the guardian receives a `Committed` status for a transfer then it can immediately proceed to signing the VAA. If it receives an `Error` status, then it should halt further processing of the transfer (see the [Handling Rejections](#handling-rejections) section for more details). If it receives a `Pending` status, then it should add the observation to a local database of pending transfers and wait for an event from the accountant. + +#### Observing Events from Wormchain + +Each guardian sets up a watcher for the accountant watching for events emitted signalling a determination on a transfer. An event for a transfer is emitted when there is a quorum of pre-observations. + +There are 2 types of events that the guardian must handle: + +- Transfer committed - This indicates that an observation has reached quorum and the transfer has been committed. The guardian must verify the contents of the event and then sign the VAA if they match. See [Threat Model](#threat-model) for more details. +- Error - This indicates that the accountant ran into an error while processing an observation. The guardian should halt further processing of the transfer. See the [Handling Rejections](#handling-rejections) section for more details + +#### Handling stuck transfers + +It may be possible that some transfers don't receive enough observations to reach quorum. This can happen if a number of guardians miss the transaction on the origin chain. Since the accountant runs before the peer to peer gossip layer, it cannot take advantage of the existing automatic retry mechanisms that the guardian network uses. To deal with this the accountant contract provides a `MissingObservations` query that returns a list of pending transfers for which the accountant does not have an observation from a particular guardian. + +Guardians are expected to periodically run this query and re-observe transactions for which the accountant is missing observations from them. + +#### Contract Upgrades + +Upgrading the contract is performed with a migrate contract governance action from the guardian network. Once quorum+1 guardians (13) have signed the migrate contract governance VAA, the [wormchain client migrate command](https://github.com/wormhole-foundation/wormhole/blob/a846036b6ebff3af6f12ff375f5c3801ada20291/wormchain/x/wormhole/client/cli/tx_wasmd.go#L148) can be ran by anyone with an authorized wallet to submit the valid governance vaa and updated wasm contract to wormchain. + +### Account Management + +The accountant keeps track of token balances for each blockchain ensuring they never become negative. When committing a transfer, the accountant parses the tokenbridge payload to get the emitter chain, recipient chain, token chain, and token address. These values are used to look up the relevant accounts and adjust the balances as needed. + +It will have the following accounts: + +- Accounts for native tokens ($SOL on solana, $ETH or $wETH on ethereum). These are classified as assets because they represent actual tokens held by smart contracts on the various chains. All tokens not issued by the token bridge are considered native tokens, even if they are wrapped tokens issued by another bridge. If the token is held by the bridge contract rather than burned, it is an asset. We'll call these "custody accounts". +- Accounts for wormhole wrapped tokens (wrapped SOL on ethereum, wrapped ETH on solana). These are classified as liabilities. These tokens are not held by the smart contract and instead represent a liability for the bridge: if a user returns a wrapped token to the bridge, the bridge **must** be able to transfer the native token to that user on its native chain. We'll call these “wrapped accounts”. + +Transferring a native token across the tokenbridge (locking) increases the balance on both the custody account and wrapped account on the destination chain. Transferring a wrapped token back across the tokenbridge (burning) and subsequently unlocking its native asset decreases the balance on both the wrapped and custody accounts. Any transfer causing the balance of an account to become negative is invalid and gets rejected. + +Each account is identified via a `(chain_id, token_chain, token_address)` tuple. Custody accounts are those where `chain_id == token_chain`, while the rest are wrapped accounts. See the [governance](#governance) section below for futher clarification. + +#### Governance + +Account balances recorded by the accountant may end up inaccurate due to bugs in the program or exploits of the token bridge. In these cases, the guardians may issue governance actions to manually modify the balances of the accounts. These governance actions must be signed by a quorum of guardians. + +#### Examples + +Example 1: A user sends SOL from solana to ethereum. In this transaction the `(Solana, Solana, SOL)` account is debited and the `(Ethereum, Solana, SOL)` account is credited, increasing the balances of both accounts. + +Example 2: A user sends wrapped SOL from ethereum to solana. In this transaction the `(Ethereum, Solana, SOL)` account is debited and the `(Solana, Solana, SOL)` account is credited, decreasing the balances of both accounts. + +Example 3: An attacker exploits a bug in the solana contract to print fake wrapped ETH and transfers it over the bridge. This could drain real ETH from the ethereum contract. Thanks to the accounting contract, the total amount drained is limited by the total number of wrapped ETH issued by the solana token bridge contract. Since the token bridge is still liable for all the legitimately issued wrapped ETH the account balances no longer reflect reality. + +Wormhole decides to cover the cost of the hack and transfers the ETH equivalent to the stolen amount to the token bridge contract on ethereum. To update the accounts, the guardians must issue 2 governance actions: one to increase the balance of the `(Ethereum, Ethereum, ETH)` custody account and one to increase the balance of the `(Solana, Ethereum, ETH)` wrapped account. + +Example 4: A user sends wrapped SOL from ethereum to polygon. Even though SOL is not native to either chain, the transaction is still recorded the same way: the `(Ethereum, Solana, SOL)` wrapped account is debited and the `(Polygon, Solana, SOL)` wrapped account is credited. The total liability of the token bridge has not changed and instead we have just moved some liability from ethereum to polygon. + +#### Creating accounts + +Since users can send any arbitrary token over the token bridge, the accountant should automatically create accounts for any new token. To determine whether the newly created account should be a custody account or a wrapped account, these steps are followed: + +- If `emitter_chain == token_chain` and a custody account doesn't already exist, create one. +- If `emitter_chain != token_chain` and a wrapped account doesn't already exist, reject the transaction. The wrapped account should have been created when the tokens were originally transferred to `emitter_chain`. +- If `token_chain != recipient_chain` and a wrapped account doesn't already exist, create one. +- If `token_chain == recipient_chain` and a custody account doesn't already exist, reject the transaction. The custody account should have been created when the tokens were first transferred from `recipient_chain`. + +### Bootstrapping / Backfills + +In order for the accountant to have accurate values for the balances of the various accounts it needs to replay all transactions since the initial launch of the token bridge. Similarly, it may be necessary to backfill missing transfers if the accountant is disabled for any period of time. + +To handle both issues, the accountant has the ability to directly process tokenbridge transfer VAAs. As long as the VAAs are signed by a quorum of guardians from the latest guardian set, the accountant uses them to update its on-chain state of token balances. + +Submitting VAAs does not disable any core checks: a VAA causing the balance of an account to become negative will still be rejected even if it has a quorum of signatures. This can be used to catch problematic transfers after the fact. + +### Handling Rejections + +Legitimate transactions should never be rejected so a rejection implies either a bug in the accountant or an actual attack on the bridge. In both cases manual intervention is required. + +#### Alerting + +Whenever a guardian observes an error (either via an observation response or via an error event), it will increase a prometheus error counter. Guardians will be encouraged to set up alerts whenever these metrics change so that they can be notified when an error occurs. + +## Deployment Plan + +- Deploy wormchain and have a quorum of guardians bring their nodes online. +- Deploy the accountant contract to wormchain and backfill it with all the tokenbridge transfer VAAs that have been emitted since inception. +- Enable the accountant in log-only mode. In this mode, each guardian will submit its observations to the accountant but will not wait for the transfer to commit before signing the VAA. This allows time to shake out any remaining implementation issues without impacting the network. +- After confidence is achieved the accountant is running correctly in log-only mode, switch to enforcing mode. In this mode, guardians will not sign VAAs for token transfers until the transfer is committed by the accountant. As long as at least a quorum of guardians submit their observations to the accountant, only a super-minority (1/3) is needed to turn on enforcing-mode for it to be effective. + +## Security Considerations + +### Threat Model + +Since the Global Accountant is implemented as an Integrity Checker, it inherits the threat model of Integrity Checkers. Therefore, it is only able to block messages and not create or modify messages. + +### Susceptibility to wormchain downtime + +Once all token transfers are gated on approval from the accountant, the uptime of the guardian network for those transfers becomes dependent on the uptime of wormchain itself. Any downtime for wormchain would halt all token transfers until the network is back online (or 2/3+ of the guardians choose to disable the accountant). + +In practice, cosmos chains are stable and wormchain is not particularly complex. However, once the accountant is live, attacking wormchain could shut down all token transfers for the bridge. + +### Token Bridge messages cannot reach quorum if 7 or more, but less than 13 Guardians decide to disable Accountant + +Due to the nature of the accountant, a guardian that chooses to enable it in enforcing-mode will refuse to sign a token transfer until it sees that the transfer is committed by the accountant. If a super-minority of guardians choose to not enable the accountant at all (i.e. stop sending their observations to the contract) then we will end up with a stalemate: the guardians with the feature disabled will be unable to reach quorum because they don't have enough signatures while the guardians in enforcing-mode will never observe the transfer being committed by the accountant (and so will never sign the observation). This is very different from [the governor feature](0007_governor.md), where only a super-minority of guardians have to enable it to be effective. diff --git a/docs/whips/0012_ibc_generic_messaging.md b/docs/whips/0012_ibc_generic_messaging.md new file mode 100644 index 00000000..eb62fcf2 --- /dev/null +++ b/docs/whips/0012_ibc_generic_messaging.md @@ -0,0 +1,90 @@ +# IBC Generic Message Emission + +## Objective + +Since Wormchain is a cosmos-sdk based chain that is IBC-enabled, we can leverage IBC generic messaging to reduce the operational burden required to run a guardian node. Wormhole guardians should, therefore, be capable of scaling to support all IBC-enabled chains at minimal cost while only running a full node for Wormchain. + +## Background + +[IBC](https://ibcprotocol.org/) is the canonical method of generic message passing within the cosmos ecosystem. IBC is part of the cosmos-sdk and Cosmos chains can enable it to connect with other cosmos chains. + +[Wormchain](https://github.com/wormhole-foundation/wormhole/tree/main/wormchain) is a cosmos-sdk based blockchain that has been purpose-built to support Wormhole. It allows the guardians to store global state on all the blockchains which Wormhole connects to, and enables a suite of product and security features. + +## Goals + +- Remove the requirement for guardians to run full nodes for IBC-enabled chains. Guardians should be able to support any IBC-enabled chain by only running a full Wormchain node. +- Define a custom IBC specification for passing Wormhole messages from IBC-enabled chains to Wormchain. +- Ensure this design is backwards-compatible with existing Cosmos integrations. +- Ensure this design does not violate any of Wormhole's existing security assumptions. + +## Non-Goals + +This document does not propose new cosmos networks for Wormhole to support. It is focused on the technical design of using IBC generic messaging to reduce the operational load on Wormhole guardians. + +This document is also not meant to describe how Wormhole can be scaled beyond the cosmos ecosystem. + +## Overview + +Currently, Wormhole guardians run full nodes for every chain that Wormhole is connected to. This is done to maximize security and decentralization. Since each guardian runs a full node for each chain, they are able to independently verify the authenticity of Wormhole messages that are posted on different blockchains. However, running full nodes has its drawbacks. Specifically, adding new chains to Wormhole has a high operational cost per chain, which makes it difficult to scale Wormhole. + +Luckily, we can leverage standards such as IBC to scale Wormhole's support for the cosmos ecosystem and other chains that implement IBC. Since IBC messages are trustlessly verified by tendermint light clients, we can pass Wormhole messages from any IBC enabled chain over IBC to Wormchain, which will then emit that message for the Wormhole guardians to pick up. This way, the Wormhole guardians only need to run a full node for Wormchain to be able to verify the authenticity of messages on all other IBC-enabled chains. + +## Detailed Design + +### External Chain -> Cosmos Chain + +This will work exactly the same way it works today. We will deploy the Wormhole cosmwasm contract stack to the cosmos chains we want to support. Wormhole relayers will post VAAs produced for any source chain directly to the cosmos destination chain. + +### Cosmos Chain -> External Chain + +Typically, the Wormhole core bridge contract emits a message which the guardians then pick up from their full nodes. + +For cosmos chains, we update the core bridge contract to instead send this message over IBC to Wormchain. Then a Wormchain contract receives the message to emit and actually emits it, which the guardians then pick up. + +Specifically, we implement two new cosmwasm smart contracts: `wormhole-ibc` and `wormchain-ibc-receiver`. + +The `wormhole-ibc` contract is meant to replace the `wormhole` core bridge contract on cosmos chains. It imports the `wormhole` contract as a library and delegates core functionality to it before and after running custom logic: +- The `wormhole-ibc` execute handler is backwards-compatible with the `wormhole` core bridge contract execute handler and it delegates all logic to the core bridge library. For messages of type `ExecuteMsg::PostMessage`, the `wormhole-ibc` contract will send the core bridge response attributes as part of an IBC message to the `wormchain-ibc-receiver` contract. + +Sending an IBC packet requires choosing an IBC channel to send over. Since IBC `(channel_id, port_id)` pairs are unique, we maintain a state variable on the `wormhole-ibc` contract that whitelists the IBC channel to send messages to the `wormchain-ibc-receiver` contract. This variable can be updated through a new governance VAA type `IbcReceiverUpdateChannelChain`. + +The `wormchain-ibc-receiver` contract will be deployed on Wormchain and is meant to receive the IBC messages the `wormhole-ibc` contract sends from various IBC enabled chains. Its responsibility is to receive the IBC message, perform validation, emit the message for the guardian node to observe, and then send an IBC acknowledgement to the source chain. This contract also maintains a whitelist mapping IBC channel IDs to Wormhole Chain IDs. The whitelist can be updated through the `IbcReceiverUpdateChannelChain` governance VAA type as well. + +### IBC Relayers + +All IBC communication is facilitated by [IBC relayers](https://ibcprotocol.org/relayers/). Since these are lightweight processes that need to only listen to blockchain RPC nodes, each Wormhole guardian can run a relayer (only some guardians running IBC relayers is also acceptable). + +The guardian IBC relayers are configured to connect the `wormchain-ibc-receiver` contract on Wormchain to the various `wormhole-ibc` contracts on the cosmos chains that Wormhole supports. + +### Guardian Node Watcher + +We will add a new IBC guardian watcher to watch the `wormchain-ibc-receiver` contract on Wormchain for the messages from the designated `wormhole-ibc` contracts on supported IBC enabled chains. This is nearly identical to the current cosmwasm watcher. The `wormchain-ibc-receiver` contract logs the Wormhole messages with the event attribute `action: receive_publish`, so the IBC watcher listens for events with this attribute. + +The new guardian watcher verifies that messages originate from the chain they claim to originate from by checking the IBC channel ID. Since the `wormchain-ibc-receiver` contract logs the channel ID the message was received over, the watcher can lookup the Wormhole chain ID that is associated with that channel ID in the `channelId -> chainId` mapping that the `wormchain-ibc-receiver` contract maintains. Once the watcher verifies that the channel ID is associated with a valid Wormhole chain ID, it will process the Wormhole message contained in the IBC packet. + +### API / database schema + +```rust +/// This is the message we send over the IBC channel +#[cw_serde] +pub enum WormholeIbcPacketMsg { + Publish { msg: Vec }, +} +``` + +## Deployment + +There are several steps required to deploy this feature. Listed in order: + +1. Deploying the new contracts: `wormhole-ibc` contracts to IBC enabled chains and the `wormhole-ibc-receiver` contract to Wormchain. +2. Upgrading existing `wormhole` contracts on IBC enabled chains to use the new `wormhole-ibc` bytecode. +3. Establishing IBC connections between the `wormhole-ibc` contracts and the `wormhole-ibc-receiver` contract. +4. Upgrading the guardian software. + +First, we need to deploy the `wormhole-ibc-receiver` contract on Wormchain. This will require 2 governance VAAs to deploy and instantiate the bytecode. + +Next, we should deploy the `wormhole-ibc` contract to the IBC-enabled chain we want to support. If that chain already has a `wormhole` core bridge contract, we can migrate the existing contract to the new `wormhole-ibc` bytecode so that we don't need to redeploy and re-instantiate the token bridge contracts with new core bridge contract addresses. + +Next, we should perform a trusted setup process with a trusted relayer to establish a connection between the `wormhole-ibc` and `wormchain-ibc-receiver` contracts. After we establish the IBC connection and upgrade the guardians to support the new `IbcReceiverUpdateChainConnection` governance VAA type, we can perform governance to add the `channelId -> chainId` mapping on the `wormchain-ibc-receiver` contract and populate the `channelId` corresponding to the `wormchain-ibc-receiver` on the `wormhole-ibc` contract. + +Finally, the guradians can upgrade their node software to use the new IBC watcher. \ No newline at end of file