Code Examples

Bitcoin Arbitrary Message


📘

BTC arbitrary message signing can be also performed by Typed Message signing and it is considered more secure. Check out our Typed Message guide


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

import { readFileSync } from 'fs';
import { 
  Fireblocks, 
  BasePath, 
  TransactionOperation, 
  TransferPeerPathType, 
  TransactionRequest, 
  TransactionResponse, 
  FireblocksResponse, 
  TransactionStateEnum, 
  CreateTransactionResponse 
} from "@fireblocks/ts-sdk";
import { createHash } from "crypto";

const FIREBLOCKS_API_SECRET_PATH = "<PATH_TO_SECRET>";
const API_KEY = "<API_KEY>"

// Initialize a Fireblocks API instance with local variables
const fireblocks = new Fireblocks({
    apiKey: API_KEY,
    basePath: BasePath.US, // Basepath.Sandbox for the sandbox env
    secretKey: readFileSync(FIREBLOCKS_API_SECRET_PATH, "utf8"),
});


const transactionPayload: TransactionRequest = {
  assetId: "BTC",
  operation: TransactionOperation.Raw,
  source: {
    type: TransferPeerPathType.VaultAccount,
    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(JSON.stringify(txInfo, null, 2));
    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"),);
    return 
  } catch (error) {
    console.error(error);
  }
};

rawSign("My message23");

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

const apiKey = "<YOUR_API_KEY>"
const apiSecretPath = "<PATH_TO_SECRET>"
const apiSecret = fs.readFileSync(path.resolve(__dirname, apiSecretPath), "utf8"); 
const fireblocks = new FireblocksSDK(apiSecret, apiKey);

async function signArbitraryMessage(fireblocks: FireblocksSDK, vaultAccountId: string, message: string, 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;
    let currentStatus = status;
  	while(currentStatus != TransactionStatus.COMPLETED && currentStatus != TransactionStatus.FAILED) {
        try {
            console.log("keep polling for tx " + id + "; status: " + currentStatus);
            txInfo = await fireblocks.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([signature.v + 31]).toString("hex") + signature.fullSig;
    console.log("Encoded Signature:", Buffer.from(encodedSig,"hex").toString("base64"));
} 
 signArbitraryMessage(fireblocks, "0", "INSERT TEXT HERE");
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.


Ripple 'Require Destination Tags' Configuration


Destination tags are a feature of XRP Ledger payments that can indicate specific purposes for payments from and to multi-purpose addresses. Destination tags do not have direct on-ledger functionality; destination tags merely provide information about how off-ledger systems should process a payment. In transactions, destination tags are formatted as 32-bit unsigned integers.

Destination tags indicate the beneficiary or destination for a payment. For example, a payment to an exchange or stablecoin issuer address can use a destination tag to indicate which customer to credit for the amount of the payment in that business's own systems. A payment to a merchant could indicate what item or cart the payment is buying.

The Require Destination Tag setting is designed for addresses that host balances for multiple people or purposes, to prevent people from sending money and forgetting to use a destination tag to identify whom to credit. When this setting is enabled on your address, the XRP Ledger rejects any payment to your address if it does not specify a destination tag.

For more information please read XRP official documentation


'Require Destination Tags' Configuration in Fireblocks

By default, 'Require Destination Tags' Configuration is not enabled for XRP accounts created in Fireblocks. However, you can activate this setting for a specific XRP account within a specific Fireblocks Vault Account. This function is not directly supported by the Fireblocks system but can be implemented through the Raw Signing feature.

Ensure you review the Raw Signing Overview guide before proceeding.


Enabling 'Require Destination Tags' Configuration with Raw Signing

You can utilize the Raw Signing script for enabling or disabling the 'Require Destination Tags' configuration offered by Fireblocks.

Please ensure the following prerequisites are met before executing the script:

  1. Raw Signing is activated in your workspace
  2. A Transaction Authorization Policy rule for Raw signing is in place
  3. You possess API access, and the API user is assigned at least an 'EDITOR' role