This is an example of using Wormhole Queries to bridge the World ID state root from Ethereum to Solana.
Enable cross-chain World ID verification so that protocols can verify their users’ identities on Solana. This is accomplished in two parts:
- Read, authenticate, and propagate the World ID state root from Ethereum to Solana.
- Allow for protocols to authenticate users’ World IDs on-chain on Solana using those roots.
Currently, the World ID state is managed on Ethereum in a privacy-preserving manner via an on-chain representation of the Semaphore set.
On Ethereum, on-chain verification of World IDs can be performed by calling verifyProof
on the World ID Identity Manager contract (example) with a proof provided by the Tree Availability Service.
On-chain verification can be made available on other blockchains and is currently available on some Layer 2 EVMs, such as Polygon, Optimism, and Base, via their native bridges, the World ID State Bridge contracts, and the State Bridge Service. Integrators can use the bridged OpWorldID and PolygonWorldID contracts as they would the World ID Identity Manager contract on Ethereum.
Wormhole Queries is a service that allows applications, developers, and users to access cross-chain data on-demand in an efficient and inexpensive manner. Queries leverages the Wormhole Guardians (some of the largest proof of stake validators in the blockchain ecosystem) to attest to cross-chain reads, enabling sub-second, authenticated cross-chain data retrieval. It currently supports querying, parsing, and verifying on all connected Wormhole EVM networks and Solana. Read more about Queries in the docs.
💡 The Queries feature relevant for WorldID is support for requesting a designated
eth_call
from a given contract on Ethereum and the ability to parse and verify the response on Solana.
The Ethereum-to-Solana State Bridge Service is responsible for monitoring the World ID contract on Ethereum for state root changes and propagating the root to Solana. It can do this by performing the following steps:
- Subscribe to
TreeChanged
events or poll forlatestRoot
.event TreeChanged(uint256 indexed preRoot, TreeChange indexed kind, uint256 indexed postRoot);
- When the root has updated, issue a Wormhole Query request for the
latestRoot
on Ethereum. - Submit the Query response to the SolanaWorldID program on Solana.
This is akin to the EVM L2 State Bridge Service
The bridge service provided in /app
is a TypeScript program designed to be run either in a scheduled lambda / cloud function setting or as a service. If the SLEEP
environment variable is set, it will run as a service, sleeping for SLEEP
seconds between calls and will not exit on errors, otherwise it will simply run once, throwing on errors. When running as a service, the CLEANUP
environment variable may also be set, which works the same as SLEEP
but for cleaning up expired roots, reclaiming their rent.
Optionally, change the following line in solana-world-id-program.ts
from it
to it.only
:
it(fmtTest("initialize", "Successfully initializes"), async () => {
Run the following to start a local validator:
anchor test --detach
Finally, run
NETWORK=localnet npm start
Running NETWORK=localnet npm start
subsequent times will update the root again, as necessary.
Akin to the above but
anchor test --detach -- --no-default-features --features testnet
and
NETWORK=testnet MOCK=true SOLANA_RPC_URL="http://127.0.0.1:8899" WALLET="../tests/keys/pFCBP4bhqdSsrWUVTgqhPsLrfEdChBK17vgFM7TxjxQ.json" npm start
NETWORK=testnet WALLET=~/.config/solana/your-key.json QUERY_API_KEY=your-wormhole-query-api-key npm start
This program serves two purposes:
- Parse, verify, and manage the bridged World ID state root.
- Provide the equivalent functionality of
verifyProof
to on-chain integrators.
This is akin to the World ID State Bridge contracts for EVM L2s and should be compatible with existing inclusion proofs served by the Tree Availability Service.
- Config stores the program configuration. There is only one.
- LatestRoot stores the most recent verified root metadata and hash. There is one per
Root
verification mechanism (e.g. Query with Guardian signatures). - GuardianSignatures stores unverified guardian signatures for subsequent verification. These are created with
post_signatures
in service of verifying a root via Queries and closed when that root is verified withupdate_root_with_query
or can be explicitly closed withclose_signatures
by the initial payer. - Root stores the metadata and expiry for a verified root. These can be closed with
clean_up_root
after the root has expired.
- initialize sets the initial config and creates the LatestRoot account. It must be signed by the deployer.
- post_signatures posts unverified guardian signatures for verification during
update_root_with_query
. - update_root_with_query with a Query response and
GuardianSignatures
account, verifies the signatures against an active guardian set and updates thelatestRoot
from the World ID Identity Manager contract on Ethereum. - clean_up_root closes a
Root
account which has expired, reimbursing the rent to the initial payer. - close_signatures allows the initial payer to close a
GuardianSignatures
account in case the query was invalid. - transfer_ownership is the first of a two-step ownership transfer process which sets the
pending_owner
and locks the ability to upgrade. - claim_ownership is the second step of the ownership transfer process, signed by either the
pending_owner
(to accept) or the existingowner
(to cancel). - set_root_expiry sets the
root_expiry
field. Theowner
must sign. - set_allowed_update_staleness sets the
allowed_update_staleness
field. Theowner
must sign. - verify_groth16_proof verifies a proof against an active root and inputs. Intended to be called via CPI by on-chain integrators, though it can be called directly as well.
anchor test
anchor build --verifiable -- --no-default-features --features testnet
anchor build --verifiable
anchor deploy --provider.cluster devnet --provider.wallet ~/.config/solana/your-key.json
NETWORK=testnet WALLET=~/.config/solana/your-key.json npx tsx app/init.ts
anchor deploy --provider.cluster mainnet --provider.wallet ~/.config/solana/your-key.json
NETWORK=mainnet WALLET=~/.config/solana/your-key.json npx tsx app/init.ts
anchor upgrade --provider.cluster <network> --provider.wallet ~/.config/solana/your-key.json --program-id <PROGRAM_ID> target/deploy/solana_world_id_program.so
If you get an error like this
Error: Deploying program failed: RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: account data too small for instruction [3 log messages]
Don't fret! Just extend the program size.
solana program -u <network> -k ~/.config/solana/w7-testnet.json extend <PROGRAM_ID> <ADDITIONAL_BYTES>
You can view the current program size with solana program -u <network> show <PROGRAM_ID>
.