This demo used zkVM to proof ownership of a bitcoin taproot address. The way it does this, is by submitting a bitcoin private key to zkVM, which will derive the public key and submit it to the Bob testnet. Because of the zero knowledge (zk) part of zkVM, no information regarding the private key gets published. The proof is generated by Bonsai and checked on-chain.
It is important to note that while zkVM does not leak information about the private key, the private key is received by the bonsai API, which means that you have to trust this service to not steal your keys. As such, it is recommended not to use this demo on account that you care about.
Because of the above, this demo is more of a demonstration of the technical possibilities rather than something to use in production. In production, other approaches are more suitable, such as signing a message with the private key, and having zkVM check that signed message using the public key.
Finally, taproot addresses are pretty flexible - the spending can be either pubkey-based (like P2PK) or script-based (like P2SH). This demo only works for taproot addresses of the former type. Specifically is written to work only to work with bitcoin-core wallets, where wallets and addresses are created using the ord
tool.
This demo has the following prerequisites:
- bitcoin-core version 23 or higher. Tests were done with version 25.
- ord for address and wallet generation.
- risc0 dependencies for the zkVM program compilation.
- a bonsai API key. The commands below will use
$API_KEY
- substitute this for your key.
The run the demo, perform the following steps:
Create a new ethereum account using e.g. metamask, and fund the account using the l2 faucet on this page. The private key of this account will be used in the commands below (substitute the DEPLOYER_PRIVATE_KEY
variable).
Deploy the necessary contracts (the Bonsai relay, verifier, and the TaprootRegister):
forge script script/Deploy.s.sol --rpc-url https://l2-fluffy-bob-7mjgi9pmtg.t.conduit.xyz --broadcast --verify --verifier blockscout --verifier-url 'https://explorerl2-fluffy-bob-7mjgi9pmtg.t.conduit.xyz/api?'
After running the command above, look for output like this:
== Logs ==
Deployed RiscZeroGroth16Verifier to [...]
Deployed BonsaiRelay to [...]
Image ID for taproot-derive is [...]
Deployed TaprootRegister to [...]
The commands below will use the $BONSAI_RELAY
and $TAPROOT_REGISTER
values - substitute those for the addresses logged above.
Start a relayer client (and leave it running in the background):
BONSAI_API_URL=https://api.bonsai.xyz/ BONSAI_API_KEY=$API_KEY cargo run --bin bonsai-ethereum-relay-cli -- run --relay-address $BONSAI_RELAY --eth-node wss://l2-fluffy-bob-7mjgi9pmtg.t.conduit.xyz --eth-chain-id 901 --private-key $DEPLOYER_PRIVATE_KEY
Start a regtest bitcoin daemon:
bitcoind -regtest -server -rpcuser=rpcuser -rpcpassword=rpcpassword -fallbackfee=0.0002 -blockfilterindex -txindex=1 -prune=0 -blockversion=4
Create an ordinals wallet:
ord --regtest --bitcoin-rpc-pass rpcpassword --bitcoin-rpc-user rpcuser wallet create
Create a new taproot address:
ord --regtest --bitcoin-rpc-pass rpcpassword --bitcoin-rpc-user rpcuser wallet receive
Now finally, initiate the proving of an address:
cargo run --bin taproot-prover -- --address 0000000000000000000000000000000000000001 --taproot-address $TAPROOT_ADDRESS_FROM_PREVIOUS_STEP --bonsai-api-key=$API_KEY
The command above, if it runs successfully, will initiate the generation of a zk-proof on the bonsai server, and after completion (which can take a couple of minutes), it will submit it to the Bob testnet for verification. After waiting a couple of minutes, you will be able to see the result in the explorer. Go to the explorer and search for the previously logged $TAPROOT_REGISTER
address. Go to the "Internal Transactions", click the latest transaction, and click "Logs". You should see an OwnershipProven
event, showing your ethereum and taproot address.
The guest program run inside the zkVM can be found in methods/guest/src/main.rs
and methods/guest/src/lib.rs
. This code takes the private key of the taproot address and a public evm address (both encoded using the ethereum abi), and outputs the derived the bech32m address. The evm address is passed through unprocessed. Note that in production you wouldn't want to do this, since you'd assume that the host is an untrusted component.
The code for extracting the private key associated with an address can be found in taproot-prover/src/main.rs
. Note that this is not very straightforward, since with descriptor wallets the rpc commands for dumping private addresses do not work.