Skip to content

Sign message

shigeyuki azuchi edited this page May 24, 2026 · 2 revisions

Bitcoin::MessageSign class provides the ability to sign and verify Bitcoin messages.

The following signature formats are supported:

Format Constant Address types Prefix
Legacy (Bitcoin Core sign-message) FORMAT_LEGACY (default) P2PKH none
Simple (BIP-322) FORMAT_SIMPLE P2WPKH, P2WSH, P2TR smp
Full (BIP-322) FORMAT_FULL All of the above ful

Note: Proof of Funds (pof) variant of BIP-322 is not supported. Calling verify_message with a pof-prefixed signature raises NotImplementedError.

Sign

Legacy format (default)

You can generate a legacy signature for any message with the following code:

message = 'Trust no one'
private_key = 'd97f5108f11cda6eeebaaa420fef0726b1f898060b98489fa3098463c0032866'
key = Bitcoin::Key.new(priv_key: private_key, key_type: Bitcoin::Key::TYPES[:compressed])

signature = Bitcoin::MessageSign.sign_message(key, message)
=> 'IPojfrX2dfPnH26UegfbGQQLrdK844DlHq5157/P6h57WyuS/Qsl+h/WSVGDF4MUi4rWSswW38oimDYfNNUBUOk='

A prefix will be assigned to the message when it is signed. The default value of the prefix is Bitcoin Signed Message:\n, but it can also be specified with the prefix: keyword argument of sign_message like:

signature = Bitcoin::MessageSign.sign_message(key, message, prefix: "Dogecoin Signed Message:\n")

BIP-322 simple format

Pass format: Bitcoin::MessageSign::FORMAT_SIMPLE together with the address being signed for. Currently a single-key API is supported (P2WPKH and P2TR key-path):

key  = Bitcoin::Key.from_wif('L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k')
addr = key.to_p2wpkh

signature = Bitcoin::MessageSign.sign_message(
  key, 'Hello World',
  format: Bitcoin::MessageSign::FORMAT_SIMPLE,
  address: addr
)
=> 'smpAkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI='

For P2TR, the private key is automatically tweaked for the key-path spend, and the resulting witness contains a single 64-byte Schnorr signature (SIGHASH_DEFAULT).

Note: Producing a simple/full signature for multisig P2WSH addresses (which requires multiple signers) is not supported by the current single-key sign_message API. Verification of such signatures works as described below.

BIP-322 full format

signature = Bitcoin::MessageSign.sign_message(
  key, 'Hello World',
  format: Bitcoin::MessageSign::FORMAT_FULL,
  address: addr
)
=> 'fulAgAAAAA...'

The full format serializes the entire to_sign transaction (rather than just the witness stack), and is the only choice when you want to attach non-default nVersion/nLockTime/nSequence to the proof.

Verify

You can validate a signature using the address and message. verify_message automatically detects the signature variant from its prefix (smp / ful) and falls back to the simple format if no prefix is present (for backward compatibility with pre-finalized BIP-322 signatures).

Bitcoin::MessageSign.verify_message('15CRxFdyRpGZLW9w8HnHvVduizdL5jKNbs', signature, message)
=> true

Supported address types for verification:

  • Legacy: P2PKH
  • BIP-322 simple: P2WPKH, P2WSH (including multisig), P2TR (key-path)
  • BIP-322 full: P2PKH, P2SH-P2WPKH, P2WPKH, P2WSH, P2SH-P2WSH, P2TR (key-path / script-path)

Errors and their meanings:

Situation Behavior
Malformed base64, unknown prefix, structurally invalid to_sign raises ArgumentError
Well-formed but signature does not verify against the message/address returns false
pof-prefixed signature raises NotImplementedError

Clone this wiki locally