Skip to content

Instantly share code, notes, and snippets.

@anthonyjoeseph
Last active April 18, 2024 11:28
Show Gist options
  • Save anthonyjoeseph/d46327a0a76cb6ad011148100419417c to your computer and use it in GitHub Desktop.
Save anthonyjoeseph/d46327a0a76cb6ad011148100419417c to your computer and use it in GitHub Desktop.
RAW signing via fireblocks API
import path from "path";
import fs from "fs";
import { coins, pubkeyToAddress } from "@cosmjs/amino";
import { fromHex, toBase64, toHex } from "@cosmjs/encoding";
import { StargateClient } from "@cosmjs/stargate";
import { encodePubkey, makeSignBytes } from "@cosmjs/proto-signing";
import { EncodeObject, Registry, makeAuthInfoBytes, makeSignDoc } from "@cosmjs/proto-signing";
import { sha256 } from "@cosmjs/crypto";
import { FireblocksSDK, TransactionOperation, TransactionStatus } from "fireblocks-sdk";
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
(async () => {
const apiSecret = fs.readFileSync(path.resolve("./fb-secret.key"), "utf8");
const fireblocks = new FireblocksSDK(apiSecret, process.env.FB_API_KEY ?? "MISSING_API_KEY");
const derivationPath = [44, 118, 0, 0, 0];
const chainId = "osmo-test-5";
const node = "https://rpc.osmotest5.osmosis.zone";
const prefix = "osmo";
const denom = "uosmo";
const toAddress = "osmo1_SOME_TESTNET_ADDRESS";
const pubKeyCompressed = await fireblocks
.getPublicKeyInfo({
algorithm: "MPC_ECDSA_SECP256K1",
compressed: true,
derivationPath,
})
.then((v) => v.publicKey as string);
const base64PubKey = toBase64(fromHex(pubKeyCompressed));
const walletAddress = pubkeyToAddress(
{
type: "tendermint/PubKeySecp256k1",
value: base64PubKey,
},
prefix
);
const stargate = await StargateClient.connect(node);
const { sequence, accountNumber } = await stargate.getSequence(walletAddress);
const encodedPubKey = encodePubkey({
type: "tendermint/PubKeySecp256k1",
value: base64PubKey,
});
const registry = new Registry();
const bodyBytes = registry.encode({
typeUrl: "/cosmos.tx.v1beta1.TxBody",
value: {
messages: [
{
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: {
fromAddress: walletAddress,
toAddress,
amount: [
{
denom,
amount: "1234567",
},
],
},
},
],
},
} as EncodeObject);
const authInfoBytes = makeAuthInfoBytes(
[
{
pubkey: encodedPubKey,
sequence,
},
],
coins(2000, denom), // fee,
350000, // gasLimit,
undefined, // feeGranter,
undefined // feePayer
);
const signDoc = makeSignDoc(bodyBytes, authInfoBytes, chainId, accountNumber);
const signBytes = makeSignBytes(signDoc);
const hashedMessage = toHex(sha256(signBytes));
const { id } = await fireblocks.createTransaction({
operation: TransactionOperation.RAW,
note: "some memo here",
extraParameters: {
rawMessageData: {
algorithm: "MPC_ECDSA_SECP256K1",
messages: [
{
content: hashedMessage,
derivationPath,
},
],
},
},
});
// poll until done
let txInfo = await fireblocks.getTransactionById(id);
while (txInfo.status !== TransactionStatus.COMPLETED) {
if (
txInfo.status === TransactionStatus.CANCELLED ||
txInfo.status === TransactionStatus.CANCELLING ||
txInfo.status === TransactionStatus.FAILED ||
txInfo.status === TransactionStatus.REJECTED ||
txInfo.status === TransactionStatus.TIMEOUT
) {
throw new Error(`Fireblocks failure. Tx id ${id} ${txInfo.status}`);
}
txInfo = await fireblocks.getTransactionById(id);
await new Promise((res) => setTimeout(res, 500));
}
const signature = txInfo.signedMessages?.[0]?.signature;
const unencoded = TxRaw.fromPartial({
bodyBytes,
authInfoBytes,
signatures: [fromHex(signature?.fullSig ?? "")],
});
const encoded = TxRaw.encode(unencoded).finish();
const result = await stargate.broadcastTx(Uint8Array.from(encoded));
console.log(JSON.stringify(result, null, 2));
})();
@anthonyjoeseph
Copy link
Author

anthonyjoeseph commented May 9, 2023

resources used:

DirectSecp256k1HdWallet.signDirect - demonstrates the proper way to encode input before signing - sha256(makeSignBytes(signDoc))

StargateClient spec - explains in detail how to make a sign doc from a tx (via bodyBytes and authInfoBytes) and broadcast it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment