b175dd43c8
* Add cspell configuration and custom dictionary The goal is to cut down on both incoming tyops, and well meaning but spammy tyop fix PRs. To run cspell locally install it and run: cspell '**/*.md' \ --config cspell.config.yaml \ --words-only \ --unique \ --quiet | sort --ignore-case * docs: cspell updates * wormchain: cspell updates * aptos: cspell updates * node: cspell updates * algorand: cspell updates * whitepapers: cspell updates * near: cspell updates * solana: cspell updates * terra: cspell updates * cosmwasm: cspell updates * ethereum: cspell updates * clients: cspell updates * cspell updates for DEVELOP document * github: run cspell github action * sdk: cspell updates * github: only run cspell on markdown files * algorand: EMMITTER --> EMITTER Suggested-by: @evan-gray * cspell: removed from dictionary Suggested-by: @evan-gray * aptos and node: cspell updates Suggested-by: @evan-gray * cosmowasm: doc updates for terra2 Suggested-by: @evan-gray * algorand: cspell updates Suggested-by: @evan-gray * algorand: cspell updates Suggested-by: @evan-gray * cspell: updated custom word dictionary This resorts the dictionary and adds a few new words from the algorand/MEMORY.md document around varints and integers. * cspell: sort the dictionary how vscode does it On macOS the sorting is locale dependent. To do this on macOS, you have to invert the case, do a character insensitive sort, and then invert the case again: LC_COLLATE="en_US.UTF-8" cspell '**/*.md' --config cspell.config.yaml \ --words-only \ --unique \ --no-progress \ --quiet \ | tr 'a-zA-Z' 'A-Za-z' \ | sort --ignore-case \ | tr 'a-zA-Z' 'A-Za-z' This requires the `LC_COLLATE` variable to be set to `en_US.UTF-8`, or it will not do the right thing. * docs: grammar clean up --------- Co-authored-by: Evan Gray <battledingo@gmail.com> |
||
---|---|---|
.. | ||
audit_test | ||
sandbox-algorand | ||
teal | ||
test | ||
.gitignore | ||
Dockerfile | ||
Dockerfile.build | ||
MEMORY.md | ||
Makefile | ||
NOTES.md | ||
Pipfile | ||
Pipfile.lock | ||
README.md | ||
TmplSig.py | ||
admin.py | ||
deploy.sh | ||
gentest.py | ||
globals.py | ||
inlineasm.py | ||
local_blob.py | ||
package-lock.json | ||
package.json | ||
requirements.txt | ||
runPythonUnitTests.sh | ||
sandbox | ||
test_contract.py | ||
testnet-update | ||
token_bridge.py | ||
vaa_verify.py | ||
wormhole_core.py |
README.md
Wormhole Support for Algorand
This directory contains the components needed to support full Wormhole functionality under the Algorand blockchain platform.
Component overview
This system is comprised of the following main components:
-
Core contract (
wormhole_core.py
): Algorand stateful contract with entrypoints for publishing messages (VAAs), verification of VAA signatures, and triggering of governance chores. This will be referred as CoreContract in this document. -
Token bridge contract (
token_bridge.py
): Algorand stateful contract supporting cross-chain bridging, exposing entrypoints for exchanging attestations, native tokens and ASAs, and triggering of governance. This will be referred as TokenBridge in this document. -
VAA verification stateless program (
vaa_verify.py
): Stateless program for verifying the signatures of a Wormhole VAA payload against the set of active guardian public keys. This will be referred as VaaVerify in this document. -
Dynamic storage stateless program (
TmplSig.py
): A stateless program that is bound to the main core and token bridge contracts to provide dynamic storage spaces addressable as a raw blob of bytes. Seelocal_blob.py
. This will be referred as TmplSig in this document.
Helper utilities and code include support PyTEAL code, deployment tools and tests.
System Architecture
TmplSig details
This stateless program code is parameterized with several values that give different output address. The stateless code will check for several transaction parameters accordingly.
Text | Replaced by |
---|---|
TMPL_ADDR_IDX |
Where storage starts interpreting the space as a raw array of bytes |
TMPL_EMITTER_ID |
Concatenation of chain Id + emitter Id in VAAs to be processed, or a hardcoded string identifying the type of information stored e.g guardian utf8 string stored in hex. |
TMPL_APP_ID |
Application Id of CoreContract, TokenBridge, etc that is specified as the opt-in target transaction |
TMPL_APP_ADDRESS |
Escrow address of the stateful contract specified in APP_ID . Used for rekey target in the transaction |
- Local-state associated with the TmplSig accounts are used as dynamic storage. The technique is to access this local storage as a plain array of bytes instead of the typical key/value structure. With the current Algorand parameters, we have 127 * 15 ~ 2K of storage to be used random-access-like.
- The contract accounts addresses are generated by compilation of a stateless code parameterized by several parameters. In the system, the following contract accounts are generated:
- Account (
seq_addr
) for storing verified sequence number bits based onchainId
,emitter
,int(vaa.sequence / MAX_BITS)
where MAX_BITS = 15240. This allows the system to reject duplicated VAAs for the last 2K sequence numbers. - Account (
guardian_addr
andnew_guardian_addr
) for storing total guardian count , the guardian public keys and guardian set expiration time.
- Account (
- Once generated, the accounts are opted-in and rekeyed to the core application.
Briefly, the semantics of the transaction when TmplSig is "attached" to a stateful app is:
- Optin of LogicSig to target stateful contract
TMPL_APP_ID
for the app to use LogicSig account local storage - Rekey of LogicSig to escrow address for the smart contract to become the sole "governor" of the LogicSig account address
NOTE: A more detailed overview of TmplSig can be found in MEMORY.md.
Core Contract: Functional Description
Initialization stage
The initialization call needs a governance VAA to be passed in, typically to setup initial guardian list. The init call will:
- store the VaaVerify hash in the
vphash
global state key - check for the creator address, set
booted
global state to1
- check for duplicate VAA
- handle the governance VAA passed as argument.
See below on how governance VAAs are processed, and how duplicate detection technique is used.
publishMessage
The publishMessage
call will retrieve the current sequence number from related TmplSig local store, increment in by 1, store the new sequence number and emit a Log message which can be picked by Wormhole network for subsequently creating a guardian-signed VAA message.
hdlGovernance
Governance messages can carry requests for:
- Update the active guardian set
- Upgrade contracts: For Algorand, an upgrade-contract governance VAA must contain the hash of the program that is approved as an upgrade (stored in global
validUpdateApproveHash
). The upgrade process itself is triggered with the update action, where the clear and approval program hashes are checked against what the governance VAA carried. If they differ, an assertion is thrown and the update call is aborted. A successful call writes an onchain Log with the new hashes and allows the update process to go on. - Setting the per-message fee
- Retrieving previously paid message fees
A governance request packed in a VAA must be verified by a verifyVaa
call in the transaction group.
vaaVerify
The VAA verify call will work by design only in a transaction group structured as:
TX | args | accounts | sender |
---|---|---|---|
verifySigs | [sigs0..n, keyset0..n, digest] | seq_addr, guardian_addr | vaa_verify_stateless |
verifySigs | ... | seq_addr, guardian_addr | vaa_verify_stateless |
verifyVAA | vaa | seq_addr, guardian_addr | foundation |
Keep in mind that depending on the number of signatures to verify there can be one or several verifySigs calls working in tandem with the VaaVerify stateless program. This depends on how many signatures we can verify on a single TX. At time of this writing, considering the opcode budget limitation of AVM 1.1, a total of nine (9) signatures can be verified at once, so for the current 19 guardians three verifySigs calls would be needed for verifying signatures 0..8, 9..17, 18.
A successful call must:
- Retrieve the guardian keys from the proper local dynamic storage
- Validate if the VAA passed in Argument #1 has enough guardians to be verified
- Check that it's not expired.
- Check that each guardian signed at most once
- Verify that each verifySigs TX is validated by the correct stateless VerifyVaa
- Verify that each verifySigs TX is verifying the expected signature subset.
- Verify that each verifySigs TX is verifying against the same guardian keys.
- Verify that each verifySigs TX is verifying the same VAA.
The vaaVerify call does allow nop (dummy) TX in the group to maximize opcode budgets and/or storage capacity. After the verifyVAA
call, a client can issue more transactions with the fact that the VAA was verified.
Appendix: Duplicate verification
To detect duplicate VAA sequence numbers the following technique is used:
- For each key in local state, there is an associated value entry. The total space of value-entries is 127*15, we have 2K of addressable space using the
LocalBlob
class. - A TmplSig stateless account is generated using the 2K space as a bit field, yielding 15240 bits. So for ~16K consecutive VAA numbers, the contract code sets a bit for identifying already verified VAAs. Based on setting the stateless
TMPL_ADDR_IDX
to formulavaa_sequence_number / 15240
, we have designated storage for marking VAAs in consecutive 16k-bit blocks.