The account model in BRC2.0 differs fundamentally from Ethereum. Understanding this before you write any authorization logic will save you from hard-to-debug security issues.
No private keys for EVM addresses
EVM addresses in BRC2.0 do not have associated private keys.
An EVM address is deterministically derived from a Bitcoin wallet’s output script (pkscript) using:
evm_address = keccak256(bitcoin_pkscript_bytes)[12:]
This address exists solely as an execution identity inside the EVM. It cannot sign messages and cannot produce Ethereum-style ECDSA signatures.
Any authentication scheme that assumes the existence of an ECDSA key for an EVM address will not work.
Different Bitcoin addresses or scripts produce different EVM addresses, even if they are controlled by the same wallet software.
Bitcoin wallet as identity
A user’s Bitcoin wallet is their identity.
Ownership and intent are proven by signing messages using Bitcoin keys and submitting those signatures alongside contract calls. The EVM address derived from the wallet script serves as the canonical on-chain identifier for that user.
BIP-322 instead of ecrecover
Ethereum’s ecrecover precompile cannot be used for authentication. Instead, authentication is performed using BIP-322 message signatures, verified inside smart contracts via a dedicated precompile.
Verification flow
User signs a message
The user signs a message using their Bitcoin wallet (BIP-322). This can be done with any compatible Bitcoin wallet.
Pass signature as calldata
The signature and the signed message are ABI-encoded and passed as calldata when inscribing the contract call.
Contract calls the BIP-322 precompile
Inside the contract, call the BIP-322 verification precompile with the message and signature.
Precompile returns the derived EVM address
If verification succeeds, the precompile returns the EVM address derived from the signing Bitcoin script. Use this address as the authenticated user.
BIP-322 precompile interface
The precompile is deployed at address 0xFE in the BRC2.0 execution environment. It takes the signing wallet’s pkscript, the message, and the BIP-322 signature, and returns a boolean indicating whether the signature is valid.
// Precompile address: 0xFE
interface IBIP322_Verifier {
function verify(
bytes calldata pkscript,
bytes calldata message,
bytes calldata signature
) external returns (bool success);
}
See the Precompiles reference for the full parameter and return documentation.
Example: authenticated action
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
IBIP322_Verifier constant BIP322 = IBIP322_Verifier(address(0xFE));
contract AuthenticatedVault {
mapping(address => uint256) public balances;
/// @notice Withdraw assets with explicit BIP-322 authorization.
/// @param pkscript The Bitcoin output script of the signing wallet.
/// @param message The message that was signed.
/// @param signature The BIP-322 signature produced by the Bitcoin wallet.
function withdraw(
uint256 amount,
bytes calldata pkscript,
bytes calldata message,
bytes calldata signature
) external {
bool valid = BIP322.verify(pkscript, message, signature);
require(valid, "Invalid BIP-322 signature");
// Derive the expected EVM address from the pkscript and compare to msg.sender
address expectedSender = address(uint160(uint256(keccak256(pkscript))));
require(expectedSender == msg.sender, "Signer must match caller");
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
// ... release assets
}
}
Interpreting msg.sender
msg.sender represents the derived EVM address of the Bitcoin script that initiated the operation. Think of it as:
“The Bitcoin script that authorized this execution.”
Not as an Ethereum-style externally owned account.
Because this address cannot sign, msg.sender alone should not be treated as proof of cryptographic intent. Any action that requires explicit user consent — withdrawals, approvals, upgrades — must additionally verify a BIP-322 signature.
Use msg.sender as a stable identifier for storing balances, permissions, and state. Use BIP-322 verification for any action that requires proof of user intent.
Authorization patterns
Recommended
Anti-patterns
Verify BIP-322 for sensitive actionsIBIP322_Verifier constant BIP322 = IBIP322_Verifier(address(0xFE));
function sensitiveAction(
bytes calldata pkscript,
bytes calldata message,
bytes calldata signature
) external {
bool valid = BIP322.verify(pkscript, message, signature);
require(valid, "Unauthorized");
// proceed
}
Map permissions to derived EVM addressesmapping(address => bool) public isAdmin;
function onlyAdmin() internal view {
require(isAdmin[msg.sender], "Not admin");
}
Use explicit nonces and domain separation for replayable actionsmapping(address => uint256) public nonces;
function buildMessage(address user, uint256 nonce) public pure returns (bytes memory) {
return abi.encodePacked("brc20:withdraw:user=", user, ":nonce=", nonce);
}
Do not use ecrecoverecrecover expects an ECDSA signature over a hash. EVM addresses in BRC2.0 have no private keys. This call will either return a meaningless address or fail entirely.// BROKEN — do not use
function verify(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
return ecrecover(hash, v, r, s); // will not return the user's EVM address
}
Do not rely on tx.originThere is no concept of a user-controlled EOA originating a call chain. tx.origin is unreliable for authorization and unsafe.// BROKEN — do not use
require(tx.origin == owner, "Not owner");
Do not assume msg.sender can produce signaturesAny pattern that asks msg.sender to prove ownership via an Ethereum signature will break. Always use BIP-322.
Do not design replayable actions without noncesWithout explicit nonce or domain separation, a signed message can be replayed across calls or contracts.
Summary
| Concept | Ethereum | BRC2.0 |
|---|
| Account type | ECDSA keypair (EOA) | Derived from Bitcoin pkscript |
| Address derivation | From private key | keccak256(pkscript)[12:] |
| Signature scheme | ecrecover | BIP-322 precompile at 0xFE |
msg.sender meaning | EOA that signed the tx | Bitcoin script that initiated op |
| User identity root | Ethereum private key | Bitcoin wallet |