Skip to content

About

Recover uncompressed public keys from Wormhole VAAs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Wormhole Key Recovery

Recover Wormhole guardians' uncompressed secp256k1 public keys from VAA signatures, then submit a guardian-set update to the Stacks Wormhole Core contract.

Background

The Stacks Wormhole Core contract stores each guardian's uncompressed secp256k1 pubkey, because Clarity has no built-in for compressed_pubkey → uncompressed_pubkey. The governance VAA only contains ETH addresses, so we have to source the uncompressed pubkeys ourselves by recovering them from real signatures.

Full details: stacks-network/stacks-core#7153.

Status

Part Description Status
1 Fetch VAAs and recover uncompressed pubkeys ✅
2 Build and sign the Stacks update-guardians-set transaction ✅
3 Broadcast the signed transaction ✅

Quick Start

npm install
npm run recover

Configuration

All settings are read from the environment. Resolution order is process.env > .env file > built-in default. Copy .env.example to .env and uncomment the lines you want to override.

Variable Purpose Default Sensitive
OPERATOR_PRIVATE_KEY Stacks operator key (hex, no 0x). Required for build-tx. — Yes
STACKS_API_URL Stacks API base URL. https://api.hiro.so No
STACKS_NETWORK mainnet or testnet. mainnet No
STACKS_TX_FEE Explicit fee in uSTX for build-tx. CLI --fee overrides. (estimator) No
WORMHOLESCAN_API_URL Wormholescan base URL. https://api.wormholescan.io No
GUARDIAN_SET_INDEX Pin recovery to a specific set index. (auto-detect) No

.env is gitignored. .env.example is committed as a template.

Commands

Command Description
npm run recover Recover keys from live wormholescan data. Generates guardians.json.
npm run recover -- --json Same, but emit JSON to stdout (logs to stderr).
npm run build-tx Build and sign the update-guardians-set tx. Generates update-guardians-set.tx and update-guardians-set.summary.json.
npm run build-tx -- --vaa <file|hex> Same, but use the supplied upgrade VAA instead of fetching the latest.
npm run build-tx -- --fee <uSTX> Same, but pin an explicit fee (overrides STACKS_TX_FEE).
npm run build-tx -- --guardian-pubkey N:0xHEX Same, but supply a pubkey for guardian index N (verified locally against the VAA's address). Repeatable.
npm run build-tx -- --unsafe-skip-preflight Bypass safety checks; pads missing guardians with zeros. Offline-inspection only — not broadcastable.
npm run broadcast-tx Broadcast the signed tx to the Stacks API.
npm test Run the test suite against checked-in fixtures (no network).
npm run fetch-fixtures Refresh tests/fixtures/data.json from wormholescan.
npm run typecheck tsc --noEmit.

One-liner to get Uncompressed Keys

npm run --silent recover -- --json 2>/dev/null | jq -r '.guardians[].uncompressedPubkey'

NOTE: --silent suppresses npm's own banner

Submitting the update

End-to-end flow once the new guardian set is live:

  1. npm run recover: writes guardians.json with all 19 recovered pubkeys.
  2. npm run build-tx: fetches the upgrade VAA, runs pre-flight checks, signs the tx, writes update-guardians-set.tx and update-guardians-set.summary.json.
  3. Inspect the signed tx:
    npx @stacks/cli decode_transaction "$(cat update-guardians-set.tx)"
  4. npm run broadcast-tx: broadcasts the signed tx and prints the explorer URL.

build-tx aborts before signing if any pre-flight check fails:

  • guardians.json is missing or incomplete (you can supply missing keys with --guardian-pubkey N:0xHEX).
  • A recovered or supplied pubkey doesn't derive to the address at the same index in the upgrade VAA.
  • The contract's active-guardian-set-id is not exactly newSetIndex - 1 (catches "already applied" or "out of order").

The signed tx is pinned to AnchorMode.OnChainOnly so it settles in an anchor block, not a microblock.

guardians.json file format

{
  "guardianSetIndex": 5,
  "recoveredAt": "2026-04-29T18:42:11.123Z",
  "stats": { "vaasInspected": 4321, "vaasUsed": 4100, "pubkeyMismatches": 0 },
  "guardians": [
    {
      "index": 0,
      "uncompressedPubkey": "0x...",              // 64 bytes (X || Y)
      "uncompressedPubkeyWithPrefix": "0x04...",  // 65 bytes (0x04 || X || Y)
      "ethAddress": "0x...",                      // 20 bytes
      "sourceVaa": "4/.../804767",
      "signatureCount": 3230
    }
  ],
  "missingGuardianIndices": []
}

Caveats

  • Depends on wormholescan's /api/v1/vaas feed.
  • Only recovers keys for guardians who have signed a VAA in the recent window.

Contributing

See ARCHITECTURE.md for the module map, protocol notes, and test layout.

About

Recover uncompressed public keys from Wormhole VAAs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages