The Problem
There is an issue in current Wormhole Core contract where we need to provide the uncompressed public keys when performing a guardian set update. We are the only chain that needs to do this, and getting these uncompressed keys can be difficult. Last time we had to wait until the new guardian set went live and manually recover the keys from VAA signatures, which led to downtime for protocols which depended on Wormhole
The need to do this is due to a limitation in Clarity, there is currently no built-in function to uncompress a secp256k1 public key
- On other chains, they can store and compare ETH addresses directly since there are built-in functions to recover from
signature → compressed_pubkey → uncompressed_pubkey → ETH_address
- On Stacks, we are missing the
compressed_pubkey → uncompressed_pubkey component. To compensate, we provide the uncompressed pubkeys, and do the following instead:
- On guardian set update, we validate the uncompressed key against the ETH addresses, derive the compressed key, and store both keys
- When validating a VAA, we recover the compressed pubkey from the message signature and compare it against the stored compressed key for the guardian
Can this be done already in Clarity?
I don't know enough about cryptography to know if this is feasible to do in Clarity, but according to Claude, it is not:
Can this be solved within existing Clarity?
No, not feasibly. Decompressing a public key requires computing a modular square root on the secp256k1 curve:
y² = x³ + 7 (mod p)
y = (x³ + 7)^((p+1)/4) (mod p)
where p is the 256-bit secp256k1 prime. This requires:
- 256-bit modular arithmetic — Clarity's largest integer is u128, so you'd need to implement big-number math by splitting values into two u128 halves and manually handling carries/overflow
- Modular exponentiation with a ~256-bit exponent — roughly 256 iterations of modular squaring + multiplication
- Each modular multiplication of 256-bit numbers composed of u128 pairs would itself be many operations
Proposed Solution
I propose adding a new Clarity built-in function, secp256k1-decompress?, which:
- Accepts a compressed secp256k1 public key:
(buff 33)
- Returns an uncompressed public key if successful:
(response (buff 64) uint)
From there, we can derive the ETH address with keccak256 and slice?
The Problem
There is an issue in current Wormhole Core contract where we need to provide the uncompressed public keys when performing a guardian set update. We are the only chain that needs to do this, and getting these uncompressed keys can be difficult. Last time we had to wait until the new guardian set went live and manually recover the keys from VAA signatures, which led to downtime for protocols which depended on Wormhole
The need to do this is due to a limitation in Clarity, there is currently no built-in function to uncompress a secp256k1 public key
signature → compressed_pubkey → uncompressed_pubkey → ETH_addresscompressed_pubkey → uncompressed_pubkeycomponent. To compensate, we provide the uncompressed pubkeys, and do the following instead:Can this be done already in Clarity?
I don't know enough about cryptography to know if this is feasible to do in Clarity, but according to Claude, it is not:
Proposed Solution
I propose adding a new Clarity built-in function,
secp256k1-decompress?, which:(buff 33)(response (buff 64) uint)From there, we can derive the ETH address with
keccak256andslice?