GitHub Logo HIP-179: External Transaction Signing for SDK and other clients

Author Danno Ferrin
Discussions-To https://github.com/hashgraph/hedera-improvement-proposal/discussions/268
Status Accepted
Needs Council Approval No
Review period ends Tue, 21 Dec 2021 07:00:00 +0000
Type Standards Track
Category Application
Created 2021-10-19
Updated 2021-12-03
Requires 30

Abstract

An RPC protocol is described that would allow clients and SDKs to sign transactions with external programs, devices, and services.

Motivation

The current Hedera SDK requires that the private keys for accounts be available and signed in process. One process is to provide the private key in an environmental variable and use local libraries to produces the signatures. This is a common approach used in Continuous Build systems and when properly handled provides good security. However, these keys can unlock large amounts of real-world value and the needed security posture needs to be better than “good.” Typical high security setups involve hardware devices storing keys or external services with actual people gate-keeping activity. A process needs to be adopted that can seamlessly support both developer grade security and full production security.

Rationale

As part of a larger DLT ecosystem Hedera should be better served by adopting existing cross-chain standards. Hence, the utility offered by the Chain Agnostic Improvement Proposal group relating to RPC interactions was chosen as the main interface. In particular CAIP-25 and CAIP-27 provide the bulk of the needed protocol. Some additional definitions of hedera signing operations is needed to finish the full loop.

User stories

As an SDK user I want to be able to configure my SDK builds to use an external signing tool to sign transactions.

As an application I want to be able to use external signing tools at build time and at runtime.

Specification

A websocket based proxy server adhering to CAIP-25 and CAIP-27 JSON-RPC calls will be used to provide signing services. Clients will need to configure and secure access to this websocket service and then use the provided handshake and provider request APIs to sign their transactions. Clients will then either need to submit the signed transaction or validate the submitted transaction was successfully submitted by the service.

Signing Proxy Server (CAIP-25 and CAIP-27)

Signing will be supported by an external web socket server. This can either be the web server of the hardware wallet or external service, or it may be a bridge between hedera and the specific APIs of the hardware wallet or signing service. The specific implementation is a detail outside the scope of this spec.

The APIs that will be used will be the CAIP-25 handshake request, the CAIP-27 provider request wrapping the hedera sign_transaction or send_transaction call specified in this HIP.

Sample call loop

To begin a signing session the SDK or app will initiate a web socket connection to the proxy. A CAIP-25 handshake call must be successfully made at that point. Zero or more signing requests can be made via the CAIP-27 provider request. The session is ended by closing the websocket connection.

If the app wishes to change the signing key then a separate websocket connection to the proxy server will need to be maintained in parallel or serialized.

Hedera signing JSON-RPC calls

Two JSON-RPC methods are defined for use in the CAIP-27 provider request, hedera_signTransaction and hedera_sendTransaction.

hedera_signTransaction

This JSON-RPC method signs a transaction that can be submitted to the network via the existing SDK methods. It is expected there will be some operation on the other end to validate the signature, possibly including user approval.

There is one required and one optional parameter: The required parameter is transaction, which will be the hex encoded protobuf message that is being signed. The optional parameter is pubKey, which is the public key of the desired signature. If the signer has more than one key this is a required field. If pubKey is specified the signature returned MUST be that of the specified key.

An example not specifying a specific key

{
    "id": 1,
    "jsonrpc": "2.0",
    "method": "hedera_signTransaction",
    "params": {
        "transaction": "fedcba9876543210"
    }
}

An example specifying a particular public key

{
    "id": 1,
    "jsonrpc": "2.0",
    "method": "hedera_signTransaction",
    "params": {
        "transaction": "fedcba9876543210",
        "pubkey": "f0e0d0c0b0a09876543210"
    }
}

The successful return will have one field signature that will be the hex encoded signature that can be added to the sigMap field of a SignedTransaction method. The transaction is not returned and must be the transaction of the request.

An example of a successful response

{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "signature": "9876543210fedcba"
    }
}

There are a number of error messages corresponding to standard failure scenarios. If the call fails for some other reason the client MAY provide any sensible error code and description

  • When user disapproves of the specific transaction
    • code = 5099
    • message = “User disapproved requested transaction”
  • When the specified public key is not available
    • code = 5098
    • message = “Public key not available”
  • When wallet rejects the transaction before presenting it to the user
    • code = 5199
    • message = “Transaction rejected by wallet provider”
  • When the wallet has multiple private keys that could sign, but the user did not specify a key
    • code = 5198
    • message = “Multiple public keys available”
    • data (optional) = Array of hex encoded public keys available to sign

An example of a user rejected transaction

{
    "id": 1,
    "jsonrpc": "2.0",
    "error": {
        "code": 5099,
        "message": "User disapproved requested transaction"
    }
}

An example of a multiple public key error

{
    "id": 1,
    "jsonrpc": "2.0",
    "error": {
        "code": 5198,
        "message": "Multiple public keys available",
        "data" : ["f0e0d0c0b0a09876543210","90e0d0c0b0a09876543210"]
    }
}

hedera_sendTransaction

This JSON-RPC method signs a transaction and then submits it to the hedera network. It is expected there will be some operation on the other end to validate the signature, possibly including user approval.

There is one required and one optional parameter: The required parameter is transaction, which will be the hex encoded protobuf message that is being signed. The optional parameter is pubKey, which is the public key of the desired signature. If the signer has more than one key this is a required field. If pubKey is specified the signature returned MUST be that of the specified key.

An example not specifying a specific key

{
    "id": 1,
    "jsonrpc": "2.0",
    "method": "hedera_sendTransaction",
    "params": {
        "transaction": "fedcba9876543210"
    }
}

An example specifying a public key

{
    "id": 1,
    "jsonrpc": "2.0",
    "method": "hedera_sendTransaction",
    "params": {
        "transaction": "fedcba9876543210",
        "pubkey": "f0e0d0c0b0a09876543210"
    }
}

The successful return will have one field transactionHash that will be the hex encoded transaction hash. This identifier can be used in Mirror Node APIs to access the submitted transaction.

An example of a successful response

{
    "id": 1,
    "jsonrpc": "2.0",
    "result": {
        "transactionHash": "9876543210fedcba"
    }
}

There are a number of error messages corresponding to standard failure scenarios. If the call fails for some other reason the client MAY provide any sensible error code and description

  • When user disapproves of the specific transaction
    • code = 5099
    • message = “User disapproved requested transaction”
  • When the specified public key is not available
    • code = 5098
    • message = “Public key not available”
  • When wallet rejects the transaction before presenting it to the user
    • code = 5199
    • message = “Transaction rejected by wallet provider”
  • When the wallet has multiple private keys that could sign, but the user did not specify a key
    • code = 5198
    • message = “Multiple public keys available”
    • data (optional) = Array of hex encoded public keys available to sign
  • When the Hedera network rejects the transaction
    • code = 5299
    • message = “Transaction rejected by Hedera network”
    • data (optional) = String providing as much of the error message as the proxy determines is appropriate

An example of a user rejected transaction

{
    "id": 1,
    "jsonrpc": "2.0",
    "error": {
        "code": 5099,
        "message": "User disapproved requested transaction"
    }
}

An example of a multiple public key error

{
    "id": 1,
    "jsonrpc": "2.0",
    "error": {
        "code": 5198,
        "message": "Multiple public keys available",
        "data" : ["f0e0d0c0b0a09876543210","90e0d0c0b0a09876543210"]
    }
}

Backwards Compatibility

These APIs do not displace the existing functionality provided by in-process signing, they exist as an additional option. Applications will continue to be able to sign transactions with a private key provided to them.

Security Implications

This protocol allows applications to remove the concerns about handling a signing key within the application and delegate that responsibility to an external program. This should improve the general security posture of an app.

Special attention needs to be paid to the endpoint’s security. There are many techniques such as firewalls, interface binding, and TLS certificates, and JSWT to ensure the identity and provenance of the use fo the endpoints. Those are orthogonal to the protocol of the request and response itself.

Rejected Ideas

Direct interaction with Hardware Wallets was rejected because it is not portable and durable as a standard for interactions. Specific applications and build systems may adopt this approach, but it is difficult to standardize in a portable fashion.

Using gRPC as the protocol is desirable, but cross chain standards have settled on JSON-RPC (via WebSockets or HTTP post) as a standard for interaction. The use of protocol buffers to encode the transactions is still a requirement as all chains have some opaque and chain specific data structure to sign.

Open Issues

None currently

References

  • CAIP-25 JSON-RPC Provide Handshake
  • CAIP-27 JSON-RPC Provider Request

Copyright/license

This document is licensed under the Apache License, Version 2.0 – see LICENSE or (https://www.apache.org/licenses/LICENSE-2.0)

Citation

Please cite this document as: