Signing a transaction without a private key (Fireblocks Raw Signing)

Hi everyone,

as part of a project my team works on, we want to provide custodial services via the Fireblocks Platform, however they don’t support Cardano Native Tokens, only ADA. If you wish to send CNTs via Fireblocks, you have to use their Raw Signing functionality.

The way it works, is that you create the transaction (inputs, outputs, ttl. etc.), serialize it, send it to Fireblocks API and after they sign it the API returns a full signature and a publicKey. It looks like this:

"signedMessages": [
            "derivationPath": [
            "algorithm": "MPC_EDDSA_ED25519",
            "publicKey": "9b4e380a414d16ced14e9cc00b6fd63ff3a22d5648a2f4a320aae0d6833921ab",
            "signature": {
                "fullSig": "add322abff775d40064c6114fde2507eccd87a621ff8784af3d9785038537303de08a30231c21a1c051d3e9d78647152c9cbfc4bd6f5d7438d57e529a484d702"
            "content": "7b0a202111202274797065223a2022556e7769746e65737365642054782042616262616763217261222c0a20202020226465736372697074696f6e223a20224c6564676572204311646c20466f726d6174222c0a202020202263626f72486578223a20223834613330303831383235383230353134383739336230316630323766333866633865393865306231323133333639393864343961636165363561666435663830616266613932356339353835383031303138323832353833393030366361666531356562663765363966306264333139636336613766333463356235623431626535323961376465386539363739643963343835333534353664346439323831623639306633306236326162623238383066626463323932373030663264376437623162313061636466354542316130303938393638306131353831633664323133633463623830336434336535353230656234633932323539356635336235353633623061373966616130343665383064303133613134393532366636323639353436663662363536653138633838323538333930303461346133613061383430316633323935393033646139356438383539383137373838323661623835353864643736346533346630633535623832626265653232386131343265373139393837633662336137613533666363343239663931396239653765336236363766623331316238323161303030633836346661313538316336643231336334636238303364343365353532306562346339323235393566353362353536336230613739666161303436653830643031336131343935323666363236393534366636623635366531383332303231613030303262626631613066356636220a7bdc"

Now, the idea is to embed this signature into the transaction as a witness, serialize it to CBOR, and then send it to the Preproduction testnet via Blockfrost.

This is how I built the witness using PyCardano. The CardanoWitness class contains the public key and the signature returned by Fireblocks.


However, when I try to submit the transaction to the blockchain via Blockfrost, I get an error message saying that the key used to sign the transaction is incorrect:

Tx submission res 400:b'{"error":"Bad Request","message":"{\\"contents\\":{\\"contents\\":{\\"contents\\":{\\"era\\":\\"ShelleyBasedEraBabbage\\",\\"error\\":[{\\"contents\\":{\\"contents\\":{\\"contents\\":{\\"contents\\":[\\"VKey (VerKeyEd25519DSIGN \\\\\\"9b4e380a414d16ced14e9cc00b6fd63ff3a22d5648a2f4a320aae0d6833921ab\\\\\\")\\"],\\"tag\\":\\"InvalidWitnessesUTXOW\\"},\\"tag\\":\\"ShelleyInAlonzoUtxowPredFailure\\"},\\"tag\\":\\"AlonzoInBabbageUtxowPredFailure\\"},\\"tag\\":\\"UtxowFailure\\"}],\\"kind\\":\\"ShelleyTxValidationError\\"},\\"tag\\":\\"TxValidationErrorInCardanoMode\\"},\\"tag\\":\\"TxCmdTxSubmitValidationError\\"},\\"tag\\":\\"TxSubmitFail\\"}","status_code":400}'

I don’t think there’s anything wrong with the key itself, so it must be that I’m doing something wrong within the transaction itself, or there’s something wrong with the way I perceive how Fireblocks’ signature should work. Can you even submit a transaction only using a 3rd party witness (Fireblocks)? Do I somehow need to tell the transaction that there’s not going to be a private key, and instead only a 3rd party signature as a witness?

I apologize in advance for a lengthy post, but I’m out of ideas and I could really use any guidance regarding this :slight_smile:

I don’t know what the Fireblocks API does internally, but it looks like you are signing the whole CBOR of the transaction body, but what actually should be signed is the hash of that.

See, e.g.:

(Moreover, your txHash argument seems to be misnamed. If it actually were the hash (and not the unhashed CBOR), TransactionBody.from_cbor(txHash) would definitely not work.)

1 Like

Hey @HeptaSean , thanks for answering!

You were right, the argument was left missnamed after doing changes to the initial code, however I was signing the hashed CBOR. I created the transaction → serialized it to CBOR → blake hashed it → sent it to Fireblocks for Raw Signing.

Are you sure about that?

Because in

the content looks like the complete body, not just the hash. Much too long for just the hash.

The point of the image was just to show the signature and the public key, it’s an early version where I was doing it wrong in the beginning, so that’s why the content is off. The image doesn’t show the content which is up to date. :confused:

Saw your code over in the IOG Discord. Yes, that is totally different. Can you post it here, too? And what error you get now exactly? Not InvalidWitnessesUTXOW anymore? Since you say that it works for other transactions, it would be strange if it was still a problem with the signing.