Raw Message Signing Example
Structuring The Raw Signing request
- Add
extraParameters
- Within this object, add
rawMessageData
- 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 the
rawMessageData
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:
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")
- Start by creating a transaction of type RAW, shown under
operation
(JS) or by usingcreate_raw_transaction()
(PY). - 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
.
BTC arbitrary message signing can be also performed by Typed Message signing and it is considered more secure. Check out our Typed Message guide
Updated 7 months ago