Raw Message Signing

❗️

Warning

Raw Signing is a premium feature and not available in production workspaces by default. If you're interested in this feature and want to see if your use case is eligible for it, please contact your Account Manager to discuss it.

However, Fireblocks Sandbox workspaces have Raw Signing enabled by default to allow for testing purposes.

Prerequisites

Overview

Raw Signing is a powerful feature within Fireblocks. As the Web3 world continues to rapidly evolve, Fireblocks provides the option to support additional chains and actions by signing an arbitrary message using the Fireblocks infrastructure.

Our WalletConnect integration and the Fireblocks Chrome Extension sometimes use Raw Signing to sign transactions initiated by dApps. You will need to add TAP rules for these dApp-initiated raw transactions to go through.

Raw message example

The example below illustrates the use of the Raw Signing API for Bitcoin arbitrary message signing:

  1. Add extraParameters.
  2. Within this object, add rawMessageData.
    See here for more detail on how to construct therawMessageData object.
  3. Within that object, add a messages array.

📘

About messages

  • Each message should contain some content. This is the content you would like to sign. It could be a serialized transaction that you have already constructed, for example, but the structure stays the same.
  • You can aggregate a maximum of 127 messages for Raw Signing.

This creates a hexed SHA256 hash for an arbitrary message.

import { createHash } from "crypto";
import { FireblocksSDK, PeerType, TransactionOperation, TransactionStatus } from "fireblocks-sdk";

async function signArbitraryMessage(fireblocks: FireblocksSDK, vaultAccountId, message, bip44addressIndex = 0) {
    const wrappedMessage = "\x18Bitcoin Signed Message:\n" +  String.fromCharCode(message.length) + message;

    const hash = createHash('sha256').update(wrappedMessage, 'utf8').digest();

    const content = createHash('sha256').update(hash).digest("hex");

    const { status, id } = await fireblocks.createTransaction({
        operation: TransactionOperation.RAW,
        assetId: "BTC",
        source: {
            type: PeerType.VAULT_ACCOUNT,
            id: vaultAccountId
        },
        note: `BTC Message: ${message}`,
        extraParameters: {
            rawMessageData: {
                messages: [{
                    content,
                    bip44addressIndex
                }]
            }
        }
    });

    let txInfo;

  	while(currentStatus != TransactionStatus.COMPLETED && currentStatus != TransactionStatus.FAILED) {
        try {
            console.log("keep polling for tx " + id + "; status: " + currentStatus);
            txInfo = await apiClient.getTransactionById(id);
            currentStatus = txInfo.status;
        } catch (err) {
            console.log("err", err);
        }
        await new Promise(r => setTimeout(r, 1000));    
    };
  
    const signature = txInfo.signedMessages[0].signature;

    console.log(JSON.stringify(signature));

    const encodedSig = Buffer.from([Number.parseInt(signature.v,16) + 31]).toString("hex") + signature.fullSig;
    console.log("Encoded Signature:", Buffer.from(encodedSig,"hex").toString("base64"));
} 
 signArbitraryMessage(fireblocks, "0", "INSERT TEXT HERE").then(console.log).catch(console.log);ireiir
import binascii
from time import sleep
from hashlib import sha256
from ecdsa.util import sigencode_der
from fireblocks_sdk import TransferPeerPath, FireblocksSDK, RawMessage, UnsignedMessage, VAULT_ACCOUNT, \
    TRANSACTION_STATUS_COMPLETED, TRANSACTION_STATUS_FAILED


def sign_arbitrary_message(vault_id: str, message: str, bip44_address_index: int = 0):
    # Hash the message with sha256 (twice per Bitcoin standard)
    content = sha256(sha256(message.encode("utf-8")).digest()).hexdigest()
    
    # Create an API call to Fireblocks - RAW signing request
    tx_response = fireblocks.create_raw_transaction(
        asset_id="BTC",
        raw_message=RawMessage(
            [UnsignedMessage(content=content, bip44addressIndex=bip44_address_index)]
        ),
        source=TransferPeerPath(VAULT_ACCOUNT, vault_id),
        note=f"BTC Message: {message}",
    )
    
    # Get the transaction's status and id
    tx_status = tx_response["status"]
    tx_id = tx_response["id"]
    
    # Wait for the transaction to complete or to fail
    while tx_status != TRANSACTION_STATUS_COMPLETED and tx_status != TRANSACTION_STATUS_FAILED:
        print(f"Tx {tx_id} is not completed yet. Status: {tx_status}")
        tx_info = fireblocks.get_transaction_by_id(tx_id)
        tx_status = tx_info["status"]
        sleep(2)
    
    # If successfully completed - get the signature and DER encode it
    if tx_status == TRANSACTION_STATUS_COMPLETED:
        signature = tx_info["signedMessages"][0]["signature"]
        r_int = int(signature["r"], 16)
        s_int = int(signature["s"], 16)
        der_encoded_sig = sigencode_der(r_int, s_int, order=0)

        print(f"DER-Encoded HEX signature: {binascii.hexlify(der_encoded_sig).decode()}")
        return
    # Else - failed
    print("Transaction Failed")

if __name__ == '__main__':
    sign_arbitrary_message("0", "my_test_message")
  1. Start by creating a transaction of type RAW, shown under operation\ (JS) or by using create_raw_transaction() (PY).
  2. Now, specify the source who is signing the message, which is the default vault (Id 0) in our case.

After sending the transaction for signing, wait for its completion so you can retrieve the signature.

The loop in the example above waits for the status of the transaction to be COMPLETED, or until an issue arises.

Once the transaction is complete, we print the full signature by accessing the transaction object signedMessages, then signature.