Raw Message Signing

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.

❗️

Warning: Not enabled by default

Please note that raw message signing is not enabled by default in all workspaces and requires contacting the Fireblocks support team to activate.

Raw message example

The example below illustrates the use of the Raw Signing API for Bitcoin proof of address ownership.

Instead of passing a simple plaintext message for signature:

  1. Add extraParameters.
  2. Within this object, add rawMessageData.
  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.

This creates a hexed SHA256 hash for a wrapped 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
def sign_arbitrary_message(fireblocks: FireblocksSDK, vault_id: str, message: str, bip44_address_index: int = 0):
    wrapped_message = "\x18Bitcoin Signed Message:\n" + str(len(message)) + message
    content = sha256(sha256(wrapped_message.encode('utf-8')).hexdigest()).hexdigest()

    status, id = fireblocks.create_transaction(
        tx_type=RAW,
        asset_id="BTC",
        source=TransferPeerPath(VAULT_ACCOUNT, vault_id),
        note=f"BTC Message: {message}",
        extra_parameters={
            "rawMessageData":
                RawMessage([UnsignedMessage(
                    content=content,
                    bip44addressIndex=bip44_address_index
                )])
        }
    ).values()

    while status != TRANSACTION_STATUS_COMPLETED and status != TRANSACTION_STATUS_FAILED:
        tx_info = fireblocks.get_transaction_by_id(id)
        status = tx_info["status"]

    signature = tx_info["signature"][0]
    encoded_signature = bytes(int(signature["v", 16]) + 31).hex() + signature["fullSig"]

    print(b64encode(bytes.fromhex(encoded_signature).decode()))
    
sign_arbitrary_message(fireblocks, "0", "INSERT TEXT HERE")
  1. Start by creating a transaction of type RAW, shown under tx_type (PY) or operation (JS).
  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, and finally - the fullSig.