Raw sign Cosmos staking transaction

Hi!
I am trying to send an ATOM staking transaction for raw signing in the following way.
I have a formed transaction in json:

"messages":[
   {
      "typeUrl":"/cosmos.staking.v1beta1.MsgDelegate",
      "value":{
         "delegatorAddress": ...
        "validatorAddress": ...
...
      }
   }
}
],
"fee":{
   "amount":[
      {
         "amount":"6500",
         "denom":"uatom"
      }
   ],
   "gas":"260000"
}

as well as its encoded version:

0a9e010a232f636f736d6f732e7374616b696e672e763162657461312e4d736744656c656761746512770a2d636f736d6f7331737339376b636a6c76656c7a386c7330327270327a6c706a64786735643068666c386b7633381234636f736d6f7376616c6f7065723133326a757a6b3067646d77757876783470687567376d33796d796174786c68393733346734771a100a057561746f6d120731303030303030

I am forming a request to create a transaction in fireblocks dev sandbox:

"extra ="{
   "rawMessageData":{
      "messages":[
         {
            "content" : tx["encodedBody"]
         }
      ]
   }
}
status, id = fireblocks.create_transaction(
  tx_type = 'RAW',
  asset_id = 'ATOM_COS_TEST',
  source=TransferPeerPath(VAULT_ACCOUNT, "0"),
  extra_parameters = extra
).values()

The transaction is sent, but when processing the transaction, an error message appears:
Too many characters

The raw signing transaction contains at least one message with over 32 bytes. Fireblocks does not allow a message with this length for this encryption protocol.

Can you please tell me what the problem is? Am I forming the request incorrectly, or do I need to pass different parameters in the content?

Hi @xor777,

My name is Alon and I am part of the Solutions Engineering team at Fireblocks.

For ATOM we recommend that you their @cosmjs SDK and their custom signer, like this:

import { encodeSecp256k1Signature } from "@cosmjs/amino";
import { AccountData, DirectSignResponse, makeSignBytes, OfflineDirectSigner } from "@cosmjs/proto-signing";
import { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { FireblocksSDK, PeerType, TransactionArguments, TransactionOperation } from "fireblocks-sdk";
import { sha256 } from "js-sha256";
import {inspect} from 'util';

export default class ATOMFireblocksSigner implements OfflineDirectSigner{

    private assetId: string;
    private note: string;

    constructor(private fbks: FireblocksSDK, private vaId: string, private testnet: boolean = false){
        this.assetId = testnet ? "ATOM_COS_TEST" : "ATOM_COS";
        this.note = undefined;
    }

    async getAccounts(): Promise<AccountData[]> {
        const pubkey = await this.fbks.getPublicKeyInfoForVaultAccount({
            assetId: this.assetId,
            vaultAccountId: parseInt(this.vaId),
            compressed: true,
            change: 0,
            addressIndex: 0
        });
        const addr = (await this.fbks.getDepositAddresses(this.vaId, this.assetId)).filter(x=>x.type==='Permanent')[0].address;

        return new Promise((r)=>{
            r(
                [
                    {
                        address: addr,
                        pubkey: Uint8Array.from(Buffer.from(pubkey.publicKey, 'hex')),
                        algo: 'secp256k1',
                    }
                ]
            )
        });
    }
    
    async signDirect (signerAddress : string , signDoc :SignDoc){
        // derived from directsecp256k1hdwallet code, important part is to hash the message and then sign it.
        const signBytes = makeSignBytes(signDoc);
        const msgHash = sha256.create().update(signBytes).hex()
        
        let txPayload: TransactionArguments = {
            assetId: this.assetId,
            source: {
                type: PeerType.VAULT_ACCOUNT,
                id: `${this.vaId}`
            },
            operation: TransactionOperation.RAW,
            extraParameters: {
                rawMessageData: {
                    messages:   [
                        {
                            content: msgHash
                        }
                    ]
                }
            }
        };

        // Wait for TX to finish here

        const pubkey = (await this.getAccounts())[0].pubkey;
        const sig = encodeSecp256k1Signature(pubkey, Uint8Array.from(Buffer.from(tx.signedMessages![0].signature.fullSig, 'hex')));
        this.note = undefined;
        return {
            signed: signDoc,
            signature: sig
        }
    }

    setNote(note: string){
        this.note = note;
    }
}

When it comes to usage within the ATOM SDK, this is the approach you can use:

const stargateClient = await SigningStargateClient.connectWithSigner(
    rpcUrl,
    signer,
    undefined
);

Where rpcUrl is the RPC URL and the signer is an instance of ATOMFireblocksSigner mentioned above.

Thanks,
Alon