Raw Message Signing Example


Structuring The Raw Signing request


  1. Add extraParameters
  2. Within this object, add rawMessageData
  3. Within that object, add a messages array.

{
  assetId: "assetId",
  operation: TransactionOperation.RAW,
  source: {
    type: PeerType.VAULT_ACCOUNT,
    id: "vaultAccountId",
  },
  note: ``,
  extraParameters: {
    rawMessageData: {
      algorithm: "MPC_ECDSA_SECP256K1"/"MPC_ECDSA_SECP256R1"/"MPC_EDDSA_ED25519", // Optional
      messages: [
       	{
          content: "<your_content_to_sign>", // Required
          bip44AddressIndex: 0, // Optional
          bip44change: 0, // Optional
          derivationPath: [44, 0, 0, 0, 0] // Optional
        }
      ]
    },
  },
}

📘

rawMessageData object:

See here for more details on how to construct therawMessageData object

📘

About messages

You can aggregate a maximum of 127 messages for Raw Signing


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

const transactionPayload: TransactionRequest = {
  assetId: "BTC",
  operation: TransactionOperation.RAW,
  source: {
    type: PeerType.VAULT_ACCOUNT,
    id: "0",
  },
  note: ``,
  extraParameters: {
    rawMessageData: {},
  },
};

let txInfo: any;

const getTxStatus = async (txId: string): Promise<TransactionResponse> => {
  try {
    let response: FireblocksResponse<TransactionResponse> =
      await fireblocks.transactions.getTransaction({ txId });
    let tx: TransactionResponse = response.data;
    let messageToConsole: string = `Transaction ${tx.id} is currently at status - ${tx.status}`;

    console.log(messageToConsole);
    while (tx.status !== TransactionStateEnum.Completed) {
      await new Promise((resolve) => setTimeout(resolve, 3000));

      response = await fireblocks.transactions.getTransaction({ txId });
      tx = response.data;

      switch (tx.status) {
        case TransactionStateEnum.Blocked:
        case TransactionStateEnum.Cancelled:
        case TransactionStateEnum.Failed:
        case TransactionStateEnum.Rejected:
          throw new Error(
            `Signing request failed/blocked/cancelled: Transaction: ${tx.id} status is ${tx.status}`,
          );
        default:
          console.log(messageToConsole);
          break;
      }
    }
    while (tx.status !== TransactionStateEnum.Completed);
    return tx;
  } catch (error) {
    throw error;
  }
};

const rawSign = async (
  message: string,
): Promise<CreateTransactionResponse | undefined> => {
  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");
  //@ts-ignore
  transactionPayload.extraParameters.rawMessageData = {
    messages: [
      {
        content,
        bip44addressIndex: 0,
      },
    ],
  };
  transactionPayload.note = `BTC Message: ${message}`;
  try {
    const transactionResponse = await fireblocks.transactions.createTransaction(
      {
        transactionRequest: transactionPayload,
      },
    );
    //@ts-ignore
    console.log(transactionPayload.extraParameters.rawMessageData);
    const txId = transactionResponse.data.id;
    if (!txId) {
      throw new Error("Transaction ID is undefined.");
    }
    txInfo = await getTxStatus(txId);
    console.log(txInfo);
    return;
  } catch (error) {
    console.error(error);
  }
  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"),
  );
};

rawSign("My message23");

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.