Skip to content
ecdsa.com

Bitcoin Message Verifier

Paste an address, the exact message and its signature. The verifier detects the format — a BIP-137 compact signature or a BIP-322 simple or full one — and checks it the way the address would be spent, entirely in your browser.

100% local — everything runs in your browser, nothing is sent to any server

Loading the verifier…

Fill in the address, the message and the signature, or load an example. The signature is checked on this page as you type.

What this verifier does not evaluate

  • Time locks: a full signature whose script needs a lock time or sequence (CLTV, CSV) is reported as unsupported, neither valid nor invalid.
  • Taproot script paths: only key-path signatures are checked; a script-path spend is reported as unsupported.
  • Other scripts: P2SH and P2WSH are checked when the script is a single key or a bare multisig; anything that needs a full script interpreter is reported as unsupported.
  • Proof of funds: a BIP-322 “pof” signature also spends real coins and is not verified here.

Two formats for one question

A signed message answers a simple question — does the person who sent this control that address? — and Bitcoin has two answers. The older one, standardised as BIP-137, is the "Bitcoin Signed Message" that Bitcoin Core's signmessage, Electrum, Trezor and Ledger produce. The newer one, BIP-322, proves control the way a real spend would, so it works for every kind of address, Taproot and multisig included. The verifier tells them apart from the signature itself; the comparison BIP-137 vs BIP-322 and the article on Bitcoin message signing explain when to use which.

BIP-137: a recoverable signature

The message is prefixed with "\x18Bitcoin Signed Message:\n" and its length, and hashed twice with SHA-256. The 65-byte signature is a compact ECDSA signature with a header byte in front. The header carries the recovery id, which lets the verifier compute the signer's public key from the signature alone (public key recovery), and the address type it was made for: 27–30 for P2PKH with an uncompressed key, 31–34 compressed, 35–38 nested SegWit (P2SH-P2WPKH), 39–42 native SegWit (P2WPKH). The recovered key is turned into an address of that type and compared with the one you entered.

Electrum writes the P2PKH header (31–34) for SegWit addresses too. The verifier accepts such a signature when the recovered key matches the SegWit address, and says so: Bitcoin Core would reject it, because Core verifies P2PKH addresses only (its "Address does not refer to key" error). A key that matches under another address type than the header names is accepted with the same caveat; a key that matches no type is reported together with the address it does belong to. BIP-137 cannot express Taproot or P2WSH addresses at all — for those, only BIP-322 exists.

BIP-322: a spend that never happens

BIP-322 hashes the message with the tagged hash "BIP0322-signed-message" and puts it into a virtual transaction, to_spend, whose single output pays the address's script. A second virtual transaction, to_sign, spends that output to an OP_RETURN. The signature is the proof that to_sign is a valid spend:

  • a simple signature (smp…, or the bare base64 that older verifiers wrote) is just the witness stack, for native SegWit and Taproot addresses;
  • a full signature (ful…) is the whole to_sign transaction, which also covers legacy and P2SH addresses.

The verifier rebuilds both transactions, checks that the full signature spends exactly the right output, and evaluates the spend with the consensus signature hashes: legacy, BIP-143 for SegWit v0 and BIP-341 with Schnorr signatures (BIP-340) for Taproot key paths. Single-key scripts and bare multisig (OP_m … OP_n OP_CHECKMULTISIG, in key order, with the empty NULLDUMMY element) are checked in P2SH, P2WSH and nested forms. Strict DER (BIP-66) is enforced; a high-s signature or an unusual sighash type is accepted with a note, as consensus does.

What it does not evaluate

The verifier evaluates the scripts it can check without a full script interpreter, and says "unsupported" — neither valid nor invalid — for the rest:

  • time locks — scripts with OP_CHECKLOCKTIMEVERIFY or OP_CHECKSEQUENCEVERIFY, whose full signatures set a lock time or sequence;
  • Taproot script paths — only key-path spends are checked;
  • other scripts — hash locks, miniscript policies and anything beyond a single key or bare multisig;
  • proof of funds (pof…) — a BIP-322 variant that also spends real coins, which needs the UTXO set.

Unsupported is not a failure: it means this page cannot decide, and a node with a full interpreter (or the signer's own wallet) must.

What runs locally

Everything: parsing, key recovery, the virtual transactions and every signature check run in your browser with @noble/curves and @scure/btc-signer, and the page sends nothing anywhere. A signature never needs a private key; if a WIF key or a seed phrase is pasted by mistake, it is refused before anything is processed. Wallets that export the armored "BEGIN BITCOIN SIGNED MESSAGE" block can paste it whole into the message field. To inspect a transaction that spends real coins, use the PSBT inspector; to see which addresses an extended public key derives, the xpub inspector.