add composable verification section

This commit is contained in:
Evan Gray 2023-02-16 15:29:03 -05:00 committed by Evan Gray
parent 2eb65aaa5e
commit 918cd1b2aa
5 changed files with 162 additions and 23 deletions

View File

@ -55,6 +55,7 @@
- [NFT Bridge Module](./technical/evm/nftLayer.md)
- [Relayer Module](./technical/evm/relayer.md)
- [Best Practices](./technical/evm/bestPractices.md)
- [Composable Verification](./technical/evm/composableVerification.md)
- [Solana](./technical/solana/overview.md)
- [Cosmos](./technical/cosmos/overview.md)
- [Algorand](./technical/algorand/overview.md)

View File

@ -223,24 +223,3 @@ These chains can _verify_ Wormhole messages submitted to them, but cannot _emit_
| Ethereum | 2 | | 0x26b4afb60d6c903165150c6f0aa14f8016be4aec |
| Terra | 3 | | terra1plju286nnfj3z54wgcggd4enwaa9fgf5kgrgzl |
| Binance Smart Chain | 4 | | 0x26b4afb60d6c903165150c6f0aa14f8016be4aec |
## Blockchain Finality Times
The goal of Wormhole is to provide high confidence that only _finalized_ messages are observed and attested. Different chains use different consensus mechanisms and so there are different finality assumptions with each one.
| Chain Name | Wormhole Chain ID | Suggested Number of Block Confirmations |
| :------------------ | :---------------- | :-------------------------------------- |
| Solana | 1 | 32 (Slots) |
| Ethereum | 2 | 2 epochs |
| Terra Classic | 3 | Instant |
| Binance Smart Chain | 4 | 15 |
| Polygon | 5 | 1 Heimdal Checkpoint |
| Avalanche (C-Chain) | 6 | 1 |
| Oasis (Emerald) | 7 | 1 |
| Aurora | 9 | 1 |
| Fantom | 10 | 1 |
| Karura | 11 | 1 |
| Acala | 12 | 1 |
| Klaytn | 13 | 1 |
| Celo | 14 | 1 |
| Terra | 18 | Instant |

View File

@ -0,0 +1,134 @@
# Composable Verification
Wormhole offers flexible [Consistency Levels](/wormhole/3_coreLayerContracts.md#consistency-levels), but more advanced integrators may want additional layers of verification.
Working examples of the following are available in [this example repo](https://github.com/wormhole-foundation/example-composable-verification).
## Additional Signers
If your project has some additional off-chain processes or checks to perform _after_ a message was emitted but _before_ it could be consumed on the receiving chain, you may want to consider adding an additional signer requirement to your integration. There are plenty of ways to achieve this concept, but an approach like the following keeps the emission and verification in your integrating contract, entirely separate from Wormhole.
### 1. Emit a unique message hash
```solidity
event LogMessageHash(bytes32 hash);
...
bytes32 messageHash = keccak256(
abi.encodePacked(encodedMessage, messageSequence)
);
emit LogMessageHash(messageHash);
```
After calling `publishMessage`, emit a hash for the message and sequence number. This way, the signature will be unique for two different instances of the same message contents. You could also make it more unique across implementations by including the sending chain id and contract address.
### 2. Sign the hash
```solidity
function getSigningHash(bytes32 _messageHash) public view returns (bytes32) {
return
keccak256(abi.encodePacked(_messageHash, block.chainid, address(this)));
}
```
It is helpful to have a utility function on the receiving side so you can generate an even more unique hash which ensures the signature you generate is intended for this receiving chain and contract address.
```typescript
const {
args: { hash },
} = sender.interface.parseLog(log);
const signingHash = await receiver.getSigningHash(hash);
const additionalSignature = await signer.signMessage(
ethers.utils.arrayify(signingHash)
);
```
Have your off-chain process pick up logs via your preferred method (like `finalized` block polling for `eth_getLogs`), perform its duties, and produce a signature.
### 3. Verify the signature
```solidity
function receiveMessage(
bytes memory _vaa,
bytes memory _additionalSignature
) public {
...
require(
verify(
keccak256(
abi.encodePacked(wormholeMessage.payload, wormholeMessage.sequence)
),
_additionalSignature
),
"invalid additional signature"
);
...
}
function verify(
bytes32 _messageHash,
bytes memory _signature
) public view returns (bool) {
bytes32 signingHash = getSigningHash(_messageHash);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(signingHash);
return recoverSigner(ethSignedMessageHash, _signature) == signerAddress;
}
```
Add another parameter to your `receiveMessage` function and after calling `parseAndVerifyVM`, verify that the additional signature checks out!
## Two-Bridge Rule
This example sends a message from Ethereum to Optimism via Wormhole _and_ sends the hash of that message via the [native bridge](https://community.optimism.io/docs/developers/bridge/messaging/). The receiver then requires both messages to agree, like requiring two keys to open a safe.
### 1. Send a unique hash natively
```solidity
/// Optimism L1-L2 bridge from https://community.optimism.io/docs/useful-tools/networks/#optimism-goerli
address public crossDomainMessengerAddr =
0x5086d1eEF304eb5284A0f6720f79403b4e9bE294;
/// Optimism bridge requires a recipient address so the message can be relayed
address public receiverL2Addr;
...
// Send the expected message hash and sequence via the native bridge
bytes32 messageHash = keccak256(
abi.encodePacked(encodedMessage, messageSequence)
);
ICrossDomainMessenger(crossDomainMessengerAddr).sendMessage(
receiverL2Addr,
abi.encodeWithSignature("expectPayload(bytes32)", messageHash),
1000000 // within the free gas limit amount
);
```
Similar to the previous example, after calling `publishMessage`, send a hash for the message and sequence number over the native bridge.
### 2. Receive the expected hash
```solidity
/// Sender contract address for confirming validity of native bridge messages
address public immutable l1SenderAddress;
/// Stores the expected payload hash
bytes32 public expectedPayloadHash;
/// Used by the native bridge to set the expected payload hash
/// This signature must match the ICrossDomainMessenger.sendMessage call in the Sender
/// @param _expectedPayloadHash The hash of the expected payload for the corresponding Wormhole message
function expectPayload(bytes32 _expectedPayloadHash) public {
require(getXorig() == l1SenderAddress, "invalid sender");
expectedPayloadHash = _expectedPayloadHash;
}
```
Again similar to a basic Wormhole integration where you [verify the emitter](./bestPractices.md#receiving-messages), verify that this message came from the expected L1 contract. This example only “expects” one message at a time, but you could just as easily make this a map like `processedMessages`.
### 3. Verify the hashes match
```solidity
require(
keccak256(
abi.encodePacked(wormholeMessage.payload, wormholeMessage.sequence)
) == expectedPayloadHash,
"unexpected payload"
);
```
After calling `parseAndVerifyVM`, verify that the hash checks out!

View File

@ -34,9 +34,11 @@ To emit a VAA, always use `publishMessage` which takes in the following argument
- The `nonce` provides a mechanism by which to group messages together within a Batch VAA. How the `nonce` is used is described below.
2. `Consistency` (uint8): the level of finality the guardians will reach before signing the message
- Consistency should be considered an enum, not an integer.
- On all EVM chains, 200 will result in an instant message, while all other values will wait for finality.
- On all EVM chains, `200` will result in an instant message
- On Ethereum, `201` will wait until the block the transaction is in is `safe`
- On BSC, the consistency denotes how many block confirmations will be waited before producing the message.
- More information about finality can be found [Here](../../reference/contracts.md)
- On the remaining EVM chains, all other values will wait for finality, but using `1` is recommended.
- More information about finality can be found [here](/wormhole/3_coreLayerContracts.md#consistency-levels)
3. `Payload` (bytes[]): raw bytes to emit
- It is up to the emitting contract to properly define this arbitrary set of bytes.

View File

@ -40,6 +40,29 @@ When passed a VAA, this function will either return the payload and associated m
---
## Consistency Levels
The goal of Wormhole is to provide high confidence that, by default, only finalized messages are observed and attested. Different chains use different consensus mechanisms and so there are different finality assumptions with each one. Some advanced integrators may want to get messages _before_ finality, which is where the `consistencyLevel` field offers chain-specific flexibility.
| Chain Name | Wormhole Chain ID | Instant | Safe | Finalized |
| :------------------ | :---------------- | :-------------- | :-------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------- |
| Solana | 1 | 0 (`confirmed`) | | 1 (`finalized`) |
| Ethereum | 2 | 200 | 201 (`safe`) | 1 (`finalized`) |
| Binance Smart Chain | 4 | 200 | | 15 (recommended blocks) |
| Polygon | 5 | 200 | | 1 ([checkpoint](https://wiki.polygon.technology/docs/pos/heimdall/checkpoint/)) |
| Avalanche (C-Chain) | 6 | 200 | | 1 (instant finality) |
| Oasis (Emerald) | 7 | 200 | | 1 (instant finality) |
| Fantom | 10 | 200 | | 1 (instant finality) |
| Karura | 11 | 200 | | 1 (safe mode) |
| Acala | 12 | 200 | | 1 (safe mode) |
| Klaytn | 13 | 200 | | 1 (instant finality) |
| Celo | 14 | 200 | | 1 (instant finality) |
| Moonbeam | 16 | 200 | | 1 ([`moon_isBlockFinalized`](https://docs.moonbeam.network/builders/build/moonbeam-custom-api/#finality-rpc-endpoints)) |
| Arbitrum | 23 | 200 | | 1 (L1 block `finalized`) |
| Optimism | 24 | 200 | [coming soon](https://community.optimism.io/docs/developers/bedrock/) | 1 (L1 block `finalized`) |
---
## Multicasting
Let's take a moment to point out that there is no destination address or chain in these functions.