Returned by all Schnorr signing methods (both standard and tweaked).
MessageSigner
Overview
The MessageSigner is a singleton instance exported from @btc-vision/transaction that provides a unified API for cryptographic message signing and verification. It supports Schnorr signatures, Taproot-tweaked Schnorr signatures, and quantum-resistant ML-DSA (FIPS 204) signatures.
Its most important feature is a set of Auto methods that transparently detect whether the code is running in a browser (with the OP_WALLET extension) or on a backend server, and delegate signing accordingly.
The full source code is available on GitHub at MessageSigner.ts.
Import
import { MessageSigner, EcKeyPair } from '@btc-vision/transaction';
import { networks, toHex } from '@btc-vision/bitcoin';Interfaces
interface SignedMessage {
readonly signature: Uint8Array; // 64-byte Schnorr signature
readonly message: Uint8Array; // SHA-256 hash of the original message
}| Name | Type | Required | Description |
|---|---|---|---|
| signature | Uint8Array | - | 64-byte Schnorr signature. |
| message | Uint8Array | - | SHA-256 hash of the original message. |
interface MLDSASignedMessage {
readonly signature: Uint8Array; // ML-DSA signature (2420-4627 bytes depending on level)
readonly message: Uint8Array; // SHA-256 hash of the original message
readonly publicKey: Uint8Array; // ML-DSA public key used for signing
readonly securityLevel: MLDSASecurityLevel; // LEVEL2, LEVEL3, or LEVEL5
}Returned by all ML-DSA signing methods. Includes additional metadata required for quantum-resistant verification.
| Name | Type | Required | Description |
|---|---|---|---|
| signature | Uint8Array | - | ML-DSA signature (2420-4627 bytes depending on level). |
| message | Uint8Array | - | SHA-256 hash of the original message. |
| publicKey | Uint8Array | - | ML-DSA public key used for signing. |
| securityLevel | MLDSASecurityLevel | - | ML-DSA security level. |
Instance Methods
isOPWalletAvailable(): booleanChecks whether the OP_WALLET browser extension is present and valid.
| Type | Description |
|---|---|
| boolean | true if window.opnet exists and satisfies the OPWallet interface, false otherwise. Always returns false in Node.js / non-browser environments. |
import { MessageSigner } from '@btc-vision/transaction';
if (MessageSigner.isOPWalletAvailable()) {
console.log('OP_WALLET detected - browser signing available');
} else {
console.log('No OP_WALLET - provide a keypair for signing');
}Auto Methods (Recommended)
The Auto methods are the primary recommended API for signing messages. They unify browser and backend signing behind a single call, making your code portable across environments. When keypair is undefined or omitted, the method attempts browser signing via OP_WALLET. When keypair is provided, it signs locally using the keypair directly.
async signMessageAuto(message: Uint8Array | string, keypair?: UniversalSigner): Promise<SignedMessage>Signs a message with a Schnorr signature, automatically selecting browser or backend signing.
| Name | Type | Required | Description |
|---|---|---|---|
| message | Uint8Array | string | Yes | The message to sign. Strings are UTF-8 encoded, then SHA-256 hashed before signing. |
| keypair | UniversalSigner | No | The signing keypair. Omit for browser (OP_WALLET) signing; provide for backend signing. |
| Type | Description |
|---|---|
| Promise<SignedMessage> | Contains the 64-byte Schnorr signature and the SHA-256 hashed message. |
| Type | Description |
|---|---|
| Error('No keypair provided and OP_WALLET is not available.') | If no keypair is given and OP_WALLET is not detected. |
async tweakAndSignMessageAuto(message: Uint8Array | string, keypair?: UniversalSigner, network?: Network): Promise<SignedMessage>Signs a message with a Taproot-tweaked Schnorr signature, automatically selecting browser or backend signing.
| Name | Type | Required | Description |
|---|---|---|---|
| message | Uint8Array | string | Yes | The message to sign. Strings are UTF-8 encoded, then SHA-256 hashed before signing. |
| keypair | UniversalSigner | No | The signing keypair. Omit for browser signing; provide for backend signing. |
| network | Network | No | Required when keypair is provided (backend signing): The Bitcoin network configuration. Ignored when using OP_WALLET. |
| Type | Description |
|---|---|
| Promise<SignedMessage> | Contains the 64-byte tweaked Schnorr signature and the SHA-256 hashed message. |
| Type | Description |
|---|---|
| Error('No keypair provided and OP_WALLET is not available.') | If no keypair is given and OP_WALLET is not detected. |
| Error('Network is required when signing with a local keypair.') | If a keypair is provided but network is omitted. |
When signing via OP_WALLET (no keypair), the wallet handles tweaking internally through wallet.web3.signSchnorr(). The network parameter is not needed in this case.
async signMLDSAMessageAuto(message: Uint8Array | string, mldsaKeypair?: QuantumBIP32Interface): Promise<MLDSASignedMessage>Signs a message with a quantum-resistant ML-DSA signature, automatically selecting browser or backend signing.
| Name | Type | Required | Description |
|---|---|---|---|
| message | Uint8Array | string | Yes | The message to sign. Strings are UTF-8 encoded, then SHA-256 hashed before signing. |
| mldsaKeypair | QuantumBIP32Interface | No | The ML-DSA keypair. Omit for browser (OP_WALLET) signing; provide for backend signing. |
| Type | Description |
|---|---|
| Promise<MLDSASignedMessage> | Contains the ML-DSA signature, hashed message, public key, and security level. |
| Type | Description |
|---|---|
| Error('No ML-DSA keypair provided and OP_WALLET is not available.') | If no keypair is given and OP_WALLET is not detected. |
Non-Auto Methods (Backend Only)
These methods sign directly with a provided keypair. They do not check for OP_WALLET and will throw errors if a valid keypair is not provided. Use these only when you are certain you are running in a backend environment with access to private keys.
signMessage(keypair: UniversalSigner, message: Uint8Array | string): SignedMessageSigns a message with a standard Schnorr signature using a local keypair.
| Name | Type | Required | Description |
|---|---|---|---|
| keypair | UniversalSigner | Yes | The signing keypair. Must contain a privateKey. |
| message | Uint8Array | string | Yes | The message to sign. Strings are UTF-8 encoded, then SHA-256 hashed. |
| Type | Description |
|---|---|
| SignedMessage | Synchronous, not a Promise. |
| Type | Description |
|---|---|
| Error('Private key not found in keypair.') | If keypair.privateKey is falsy. |
| Error('backend.signSchnorr is not available.') | If the ECC backend is not initialized. |
tweakAndSignMessage(keypair: UniversalSigner, message: Uint8Array | string, network: Network): SignedMessageSigns a message with a Taproot-tweaked Schnorr signature using a local keypair. Internally applies TweakedSigner.tweakSigner(keypair, { network }) to derive a tweaked keypair, then delegates to signMessage.
| Name | Type | Required | Description |
|---|---|---|---|
| keypair | UniversalSigner | Yes | The signing keypair. Must contain a privateKey. |
| message | Uint8Array | string | Yes | The message to sign. |
| network | Network | Yes | The Bitcoin network. Required for computing the Taproot tweak. |
| Type | Description |
|---|---|
| SignedMessage | Synchronous. |
signMLDSAMessage(mldsaKeypair: QuantumBIP32Interface, message: Uint8Array | string): MLDSASignedMessageSigns a message with a quantum-resistant ML-DSA signature using a local keypair.
| Name | Type | Required | Description |
|---|---|---|---|
| mldsaKeypair | QuantumBIP32Interface | Yes | The ML-DSA keypair from BIP32 quantum wallet derivation. Must contain a privateKey. |
| message | Uint8Array | string | Yes | The message to sign. |
| Type | Description |
|---|---|
| MLDSASignedMessage | Synchronous. |
| Type | Description |
|---|---|
| Error('ML-DSA private key not found in keypair.') | If mldsaKeypair.privateKey is falsy. |
Browser Wallet Methods
These methods interact directly with the OP_WALLET browser extension. They return null when OP_WALLET is not available (instead of throwing), making them safe to call speculatively. The Auto methods use these internally.
async trySignSchnorrWithOPWallet(message: Uint8Array | string): Promise<SignedMessage | null>Attempts to sign a message via the OP_WALLET browser extension using Schnorr. Internally hashes the message with SHA-256, converts to hex, and calls wallet.web3.signSchnorr(messageHex).
| Name | Type | Required | Description |
|---|---|---|---|
| message | Uint8Array | string | Yes | The message to sign. |
| Type | Description |
|---|---|
| Promise<SignedMessage | null> | Returns null if OP_WALLET is not available. Otherwise returns the signed message from the wallet extension. |
async trySignMLDSAWithOPWallet(message: Uint8Array | string): Promise<MLDSASignedMessage | null>Attempts to sign a message via the OP_WALLET browser extension using ML-DSA. Internally hashes the message with SHA-256, converts to hex, and calls wallet.web3.signMLDSAMessage(messageHex).
| Name | Type | Required | Description |
|---|---|---|---|
| message | Uint8Array | string | Yes | The message to sign. |
| Type | Description |
|---|---|
| Promise<MLDSASignedMessage | null> | Returns null if OP_WALLET is not available. Otherwise returns the ML-DSA signed message including the public key and security level from the wallet. |
async verifyMLDSAWithOPWallet(message: Uint8Array | string, signature: MLDSASignedMessage): Promise<boolean | null>Verifies an ML-DSA signature via the OP_WALLET browser extension.
| Name | Type | Required | Description |
|---|---|---|---|
| message | Uint8Array | string | Yes | The original message that was signed. |
| signature | MLDSASignedMessage | Yes | The full ML-DSA signed message object to verify. |
| Type | Description |
|---|---|
| Promise<boolean | null> | Returns null if OP_WALLET is not available. Returns true or false for the verification result from the wallet. |
async getMLDSAPublicKeyFromOPWallet(): Promise<Uint8Array | null>Retrieves the user's ML-DSA public key from the OP_WALLET browser extension.
| Type | Description |
|---|---|
| Promise<Uint8Array | null> | Returns null if OP_WALLET is not available. Otherwise returns the ML-DSA public key as a Uint8Array. |
Verification Methods
All verification methods are synchronous and work in both browser and backend environments. They do not require private keys.
verifySignature(publicKey: Uint8Array, message: Uint8Array | string, signature: Uint8Array): booleanVerifies a standard Schnorr signature against a public key and message.
| Name | Type | Required | Description |
|---|---|---|---|
| publicKey | Uint8Array | Yes | The signer's public key (33-byte compressed or 32-byte x-only). Internally converted to x-only via toXOnly. |
| message | Uint8Array | string | Yes | The original message (not the hash). The method hashes it with SHA-256 internally. |
| signature | Uint8Array | Yes | The 64-byte Schnorr signature to verify. |
| Type | Description |
|---|---|
| boolean | true if the signature is valid, false otherwise. |
| Type | Description |
|---|---|
| Error('Invalid signature length.') | If signature.length !== 64. |
| Error('backend.verifySchnorr is not available.') | If the ECC backend is not initialized. |
tweakAndVerifySignature(publicKey: Uint8Array, message: Uint8Array | string, signature: Uint8Array): booleanVerifies a Taproot-tweaked Schnorr signature. Internally tweaks the provided public key with EcKeyPair.tweakPublicKey() then delegates to verifySignature.
| Name | Type | Required | Description |
|---|---|---|---|
| publicKey | Uint8Array | Yes | The signer's untweaked public key. The method applies the Taproot tweak internally. |
| message | Uint8Array | string | Yes | The original message (not the hash). |
| signature | Uint8Array | Yes | The 64-byte tweaked Schnorr signature. |
| Type | Description |
|---|---|
| boolean |
verifyMLDSASignature(mldsaKeypair: QuantumBIP32Interface, message: Uint8Array | string, signature: Uint8Array): booleanVerifies a quantum-resistant ML-DSA signature.
| Name | Type | Required | Description |
|---|---|---|---|
| mldsaKeypair | QuantumBIP32Interface | Yes | An ML-DSA keypair containing the public key. Can be a full keypair or a public-key-only keypair created via QuantumBIP32Factory.fromPublicKey(). |
| message | Uint8Array | string | Yes | The original message (not the hash). |
| signature | Uint8Array | Yes | The ML-DSA signature bytes. |
| Type | Description |
|---|---|
| boolean |
Unlike Schnorr verification which takes a raw public key, ML-DSA verification requires a keypair object because it embeds security level information and the ML-DSA verification algorithm structure. If you only have a raw public key, reconstruct a keypair with QuantumBIP32Factory.fromPublicKey().
Utility Methods
sha256(message: Uint8Array): Uint8ArrayComputes the SHA-256 hash of a message.
| Name | Type | Required | Description |
|---|---|---|---|
| message | Uint8Array | Yes | The data to hash. |
| Type | Description |
|---|---|
| Uint8Array | 32-byte SHA-256 digest. |
All signing methods call sha256 internally before signing. You do not need to pre-hash messages unless you have a specific reason to do so (such as signing the same hash with multiple algorithms).