A Starknet account contract that enables Bitcoin wallets to control Starknet accounts via BIP-322 message signing
This project implements a Starknet account contract that verifies BIP-322 Bitcoin message signatures, allowing Bitcoin wallets to directly control Starknet accounts through Starknet's account abstraction.
BIP-322 (Bitcoin Improvement Proposal 322) defines a generic message signing format for Bitcoin. Unlike the legacy Bitcoin message signing (which only worked for P2PKH addresses), BIP-322 supports all Bitcoin address types including:
- P2WPKH (Native SegWit) -
bc1q...addresses ✅ (supported by this contract) - P2SH-P2WPKH (Wrapped SegWit)
- P2TR (Taproot)
BIP-322 works by creating a virtual "to-sign" transaction that commits to the message, then producing a witness that proves ownership of the address.
Starknet's native account abstraction allows custom signature verification logic in smart contracts. This project leverages that capability to:
- Store a Bitcoin public key hash (hash160 of the compressed public key) as the account owner
- Verify BIP-322 signatures on-chain using the
alexandria_btclibrary - Enable Bitcoin wallets to sign Starknet transactions without any intermediary
┌─────────────────┐ ┌───────────────────┐ ┌──────────────────┐
│ Bitcoin Wallet │ ──────► │ BIP-322 Message │ ──────► │ Starknet Account │
│ (e.g. Xverse) │ signs │ Signature │ verifies│ Contract │
└─────────────────┘ └───────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐
│ Execute Starknet │
│ Transactions │
└──────────────────┘
The account supports two execution modes:
Standard Starknet account execution where the BIP-322 account signs and broadcasts transactions directly:
┌────────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ BTC Wallet │────►│ Sign tx hash │────►│ BtcAccount │
│ signs message │ │ (hex string) │ │ __validate__() │
└────────────────┘ └─────────────────┘ └────────┬─────────┘
│
▼
┌──────────────────┐
│ __execute__() │
│ Execute calls │
└──────────────────┘
The message signed is the transaction hash as a 64-character hex string (lowercase, padded with leading zeros).
Enables gasless/sponsored transactions where a third party can execute transactions on behalf of the BIP-322 account:
┌────────────────┐ ┌─────────────────────┐ ┌───────────────────┐
│ BTC Wallet │────►│ Sign OutsideExec │────►│ Sender/Paymaster │
│ signs message │ │ JSON structure │ │ broadcasts tx │
└────────────────┘ └─────────────────────┘ └─────────┬─────────┘
│
▼
┌───────────────────────────┐
│ BtcAccount │
│ execute_from_outside_v2()│
└───────────────────────────┘
This implements the SNIP-9 standard for outside execution.
Unlike Ethereum which has EIP-712 for typed structured data signing, Bitcoin has no standardized typed data format. BIP-322 only supports signing arbitrary byte strings.
This creates several challenges:
- Messages are signed as plain strings - Users see raw JSON in their Bitcoin wallet
- Exact format matching is critical - The on-chain contract must reconstruct the exact same string
- Case sensitivity matters -
"Caller"≠"caller" - Whitespace and ordering matter - Any difference in JSON structure will cause verification failure
For outside execution, the signed message is a JSON structure:
{
"userAddress": "0x0203aeb387aa85d858b170f62c8b27b43f877f6d765de16d4967c3d3677d49bc",
"primaryType": "OutsideExecution",
"domain": {
"name": "Account.execute_from_outside",
"version": "2",
"chainId": "0x534e5f5345504f4c4941",
"revision": "1"
},
"message": {
"Caller": "0x...",
"Nonce": "0x...",
"Execute After": "0x...",
"Execute Before": "0x...",
"Calls": [...]
}
}
⚠️ Important: The JSON keys use specific casing (e.g.,"Execute After"with space and capitals). Both the signing client and on-chain contract must produce byte-identical strings.
💡 Tip: Examples and utilities for generating correctly formatted messages can be found in the
bip322-account-examples/directory. Seebip322-utils.tsfor message formatting helpers.
┌─────────────────────────────────────────────────────────────────────┐
│ BtcAccountPreset │
│ ┌─────────────────────────────────────────────────────────────────┐│
│ │ BtcAccountComponent ││
│ │ ┌───────────────┐ ┌───────────────┐ ┌─────────────────────┐ ││
│ │ │ Storage │ │ SRC6 Impl │ │ SRC9_V2 Impl │ ││
│ │ │ ─────────────-│ │ ─────────────-│ │ ───────────────────-│ ││
│ │ │ pubkey_hash │ │ __execute__ │ │ execute_from_ │ ││
│ │ │ nonces │ │ __validate__ │ │ outside_v2 │ ││
│ │ └───────────────┘ │ is_valid_sig │ └─────────────────────┘ ││
│ │ └───────────────┘ ││
│ │ ┌─────────────────────────────────────────────────────────────┐││
│ │ │ Internal Functions │││
│ │ │ • _is_valid_signature() - Verify BIP-322 signatures │││
│ │ │ • validate_signature_with_message() - Message verification │││
│ │ │ • get_btc_address() - Convert pubkey_hash to bech32 address │││
│ │ └─────────────────────────────────────────────────────────────┘││
│ └─────────────────────────────────────────────────────────────────┘│
│ ┌────────────────────┐ ┌────────────────────────────────────────┐ │
│ │ SRC5Component │ │ UpgradeableComponent │ │
│ │ (introspection) │ │ (contract upgrades) │ │
│ └────────────────────┘ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
| Component | Description |
|---|---|
BtcAccountComponent |
Core account logic with BIP-322 signature verification |
BtcAccountPreset |
Ready-to-deploy account contract with upgradability |
SRC6 |
Standard Starknet account interface (__execute__, __validate__) |
SRC9_V2 |
Outside execution interface for gasless transactions |
This project heavily relies on the alexandria_btc library from Alexandria for Bitcoin-related cryptographic operations:
bip322_msg_hash_p2wpkh- Computes the BIP-322 message hash for P2WPKH addressesverify_ecdsa_signature_auto_recovery- Verifies secp256k1 ECDSA signatureshash160_from_byte_array- Computes RIPEMD160(SHA256(data))decode_address/pubkey_hash_to_address- Bech32 address encoding/decoding
[dependencies]
alexandria_btc = "0.9"
alexandria_encoding = "0.9"
alexandria_bytes = "0.9"
openzeppelin_account = "2.0.0"- Scarb v2.15.0
- Starknet Foundry v0.54.1
# Clone the repository
git clone https://github.com/keep-starknet-strange/bip-322-account.git
cd bip-322-account
# Build the contracts
scarb buildsnforge testscarb build --releaseThe compiled contracts will be available in target/release/:
starknet_bip322_account_BtcAccountPreset.contract_class.jsonstarknet_bip322_account_BtcAccountPreset.compiled_contract_class.json
To declare the contract on Starknet (e.g., Sepolia testnet):
sncast --account <YOUR_ACCOUNT> declare \
--contract-name BtcAccountPreset \
--url <RPC_URL>This will output a class hash that you'll use for deployment.
To deploy a new BIP-322 account, use the class hash from the declaration step:
sncast --account <YOUR_ACCOUNT> deploy \
--class-hash <CLASS_HASH> \
--arguments '"<BITCOIN_ADDRESS>"' \
--url <RPC_URL>Example:
# Deploy with a P2WPKH Bitcoin address
sncast --account my-account deploy \
--class-hash 0x13ec6645b98e4c3b64e9c74fe4331b43907ea1fd8ae94547d7174976f91859d \
--arguments '"bc1q4gsm3wweqg66aynahflxtqx9540gspcmthehxj"' \
--url https://starknet-sepolia.public.blastapi.io/rpc/v0_8Note: The Bitcoin address must be a valid P2WPKH (native SegWit) address starting with
bc1q.... The address is passed as a ByteArray, hence the nested quotes'"..."'.
See the bip322-account-examples/ directory for TypeScript examples demonstrating:
| Example | Description |
|---|---|
sign-and-send.ts |
Direct transaction signing and execution |
outside-execution-bip322.ts |
Gasless transactions via outside execution |
paymaster-bip322.ts |
Paymaster-sponsored transactions |
cd bip322-account-examples
npm install
# Configure .env with your Bitcoin private key and Starknet RPC
npx ts-node src/sign-and-send.ts- BIP-322: Generic Signed Message Format
- SNIP-9: Outside Execution
- Alexandria Library
- OpenZeppelin Cairo Contracts
THIS SOFTWARE IS EXPERIMENTAL AND PROVIDED "AS IS".
- Not audited: The contracts and code in this repository have NOT been audited by a professional security firm.
- Use at your own risk: This software is intended for educational and experimental purposes only. Do not use with significant funds.
- No warranty: The authors make no guarantees about the security, correctness, or reliability of this software.
- Testnet recommended: It is strongly recommended to test thoroughly on Starknet testnet (Sepolia) before any mainnet usage.
If you plan to use this in a production environment, please conduct a thorough security audit first.
This project is licensed under the MIT License. See the LICENSE file for details.