How to sign using ledger "send_dfx" to maintain full air gap

Hello everyone,

I’m trying to not transfer tokens by having a private key on a networked computer using dfx ledger transfer. I’d like to accomplish this by signing a message on the air gapped computer and then moving that message.json to networked computer for a token transfer. It seems in order to transfer, you have to import your pem key on your networked computer into a dfx identity to transfer. This is not ideal.

I’ve made it to the point of understanding this will use the ledger.did and the method to invoke looks like this:

"send_dfx : (SendArgs) -> (BlockHeight)"

Here is my attempted “SendArgs” but having a bit of trouble with the syntax. Let’s solve this so we can maintain full air gap.

request:
  "sign $CANISTER send_dfx args..."
response:
  "send_dfx : (SendArgs) -> (BlockHeight)"
------------------------------------------------------------------
  
body:

(
  record {
    memo = record {
      Memo = nat64 {
        '$MEMO:nat64'
      }
    };

    amount = record {
      ICPTs = record {
        e8s = nat64 {
          '$AMOUNT:nat64'
        }
      }
    };

    to = record {
      AccountIdentifier = text {
        '$TO_ACCOUNT:text'
      }
    }

    from_subaccount = opt SubAccount {
      vec {
        '$ACCOUNT_ID_WITHOUT_CHECKSUM_BYTES'
      }
    }
  }
)
1 Like

This seems very similar to this flow:

Yes, but the question is around the “send_dfx” argument body. We know that we can sign any message, constructing the message here can be the challenge in getting all arguments right and formatted properly to their corresponding object structure.

See the approach detailed here: https://forum.dfinity.org/t/how-to-read-the-result-of-sign-send-just-as-we-do-with-call/3652?u=ori

Using the dfx sign and dfx send commands: https://sdk.dfinity.org/docs/developers-guide/cli-reference/dfx-canister.html#_dfx_canister_send

And request-status: https://sdk.dfinity.org/docs/developers-guide/cli-reference/dfx-canister.html#_dfx_canister_request_status

1 Like

didc is giving me a valid hash. Getting close but getting an error when inspecting the request-status:

content: Request IDs must be for requests signed by the caller" with the below message.

Here is the message body signed to send_dfx

(
  record {
    memo = record { 
      Memo =[YOUR MEMO INDEX IDENTIFIER]:nat64 
    };
    
    amount = record { 
      ICPTs = record {
        e8s = [YOUR TOKEN AMOUNT TO SEND]:nat64
      } 
    };
    
    fee = record { 
      ICPTs = record {
        e8s = 10000:nat64
      }
    };

    from_subaccount = opt record {
      vec {
        $ACCOUNT_ID_WITHOUT_CHECKSUM_BYTES:nat8
      }
    };

    to = record {
      AccountIdentifier = "[YOUR RECIPIENT WALLET ADDRESS]"
    };
  }
)

I also get the same problem:

Yeah, stuck here now:

The replica returned an HTTP Error: Http Error: status 403 Forbidden, content type "", content: Request IDs must be for requests signed by the caller.

I made a script that can do this, but first a warning: this is experimental and not well tested, use it at your own risk!

Paul, the script works for sending from an AG setup. Thank you.

One thing that I also see you mentioning in the comments of your script and I also can’t seem to get an answer for here: How to read the result of sign/send just as we do with call - #4 by Ori

How can we get the request status of a request from the networked computer? Not really needed for sending, but for other informational things like the get_full_neuron info call.

1 Like

Thank you for this. Could you explain how to make this work? Thanks in advance.

So in the new dfx release, 0.7.1, you can get the result of a request status request.
What you’d do it is after the send you’d do send message.json --status

When using dfx canister sign to generate a update message, a corresponding request_status message is also signed and append to the json as signed_request_status . Then after sending the update message, the user can check the request_status using dfx canister send message.json --status .

However the result is actually a Certificate type that you’ll have to paste into cbor.me and decode…this will be improved in the upcoming releases

2 Likes

That’s great. So just pass a --status and then you get back a cbor certificate type?

Yes, something like

dfx canister --no-wallet send message-inc.json --status
1 Like

Thanks very much for this clue. I’ve gotten to the point in cbor.me that I see a bunch of output like \x83\x02Nrequest_status\x83\x01... Would you mind letting me know, what am I looking at here? Is it ascii mixed with other stuff? and how do I decode it? Many thanks!

Based on this topic: Decode canister response to query request - #2 by chenyan

I’m pretty sure you mean the response needs to be decoded by didc. Is that correct?

But it’s still rather confusing:

  1. Is it really part of the process to ‘find and replace’ \x with \, to end up with a blob?
  2. What part of the overal response is to be decoded by didc? The whole thing? Everthing after the DIDL?
  3. I’m always getting this error no matter all the permutations I experiment with:

Error: Candid parser error: Unknown excape character b at 180..182

Can you please provide some more details about what you mean by “and decode”?

Thank you