Help Center
ADR 0014: Signing Runtime Transactions with Hardware Wallet
Component
Oasis SDK
Changelog
- 2023-01-23:
- Fix Deoxys-II field description in Signing encrypted runtime transactions section.
- Rename
SIGN_PT_instructions in APDUSPEC toSIGN_RT_for consistency with oasis-core and oasis-sdk codebase.
- 2023-01-03:
- Add Sapphire runtime ID and consensus address on Mainnet.
- 2022-12-13:
- Fix Secp256k1 public key size.
- 2022-10-12:
- Add Sapphire runtime ID and consensus address on Testnet,
- Remove redundant
sig_contextfromMeta, - Require
tx.call.formatto be either0or1.
- 2022-07-15: Initial public version
Status
Proposed
Context
This document proposes additions to APDU specification, guidelines for parsing runtime transactions and general UI/UX for signing them on a hardware wallet:
- APDUSPEC additions
- Changes to Allowance transaction
- Signing general runtime transactions,
- Signing smart contract runtime transactions,
- Signing EVM runtime transactions.
- Signing encrypted runtime transactions,
Test vectors
Test vectors for all runtime transactions in this ADR can be generated by using gen_runtime_vectors tool as part of the Oasis SDK.
Runtime transaction format
The format of the runtime transaction to be signed by the hardware wallet is the following:
/// Transaction.
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
pub struct Transaction {
#[cbor(rename = "v")]
pub version: u16,
pub call: Call,
#[cbor(rename = "ai")]
pub auth_info: AuthInfo,
}
The transaction can be signed with Secp256k1 (“Ethereum”), Ed25519 key, or Sr25519 key! Information on this along with the gas fee is stored inside ai field.
call is defined as follows:
/// Method call.
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
pub struct Call {
/// Call format.
#[cbor(optional, default)]
pub format: CallFormat,
/// Method name.
#[cbor(optional, default, skip_serializing_if = "String::is_empty")]
pub method: String,
/// Method body.
pub body: cbor::Value,
/// Read-only flag.
///
/// A read-only call cannot make any changes to runtime state. Any attempt at modifying state
/// will result in the call failing.
#[cbor(optional, default, rename = "ro")]
pub read_only: bool,
}
If format is:
0, the transaction is unencrypted,1, the transaction is encrypted,- any other, the transaction should be rejected with
unsupported call formaterror unless implemented outside the scope of this ADR.
method contains the name of the runtime module followed by . and the method name. If format is 1, method is empty.
body contains a CBOR-encoded transaction. If format equals 1, body contains CBOR-encoded CallEnvelopeX25519DeoxysII which contains the encrypted transaction body inside its data field.
Decision
APDUSPEC additions
GET_ADDR_SECP256K1
Command
| Field | Type | Content | Expected |
|---|---|---|---|
| CLA | byte (1) | Application Identifier | 0x05 |
| INS | byte (1) | Instruction ID | 0x04 |
| P1 | byte (1) | Request User confirmation | No = 0 |
| P2 | byte (1) | Parameter 2 | ignored |
| L | byte (1) | Bytes in payload | (depends) |
| Path[0] | byte (4) | Derivation Path Data | 44 |
| Path[1] | byte (4) | Derivation Path Data | 60 |
| Path[2] | byte (4) | Derivation Path Data | ? |
| Path[3] | byte (4) | Derivation Path Data | ? |
| Path[4] | byte (4) | Derivation Path Data | ? |
The first three items in the derivation path are hardened.
Response
| Field | Type | Content | Note |
|---|---|---|---|
| PK | byte (33) | Public Key | |
| ADDR | byte (??) | Hex addr | |
| SW1-SW2 | byte (2) | Return code | see list of return codes |
GET_ADDR_SR25519
Command
| Field | Type | Content | Expected |
|---|---|---|---|
| CLA | byte (1) | Application Identifier | 0x05 |
| INS | byte (1) | Instruction ID | 0x03 |
| P1 | byte (1) | Request User confirmation | No = 0 |
| P2 | byte (1) | Parameter 2 | ignored |
| L | byte (1) | Bytes in payload | (depends) |
| Path[0] | byte (4) | Derivation Path Data | 44 |
| Path[1] | byte (4) | Derivation Path Data | 474 |
| Path[2] | byte (4) | Derivation Path Data | ? |
| Path[3] | byte (4) | Derivation Path Data | ? |
| Path[4] | byte (4) | Derivation Path Data | ? |
The first three items in the derivation path are hardened.
Response
| Field | Type | Content | Note |
|---|---|---|---|
| PK | byte (32) | Public Key | |
| ADDR | byte (??) | Bech 32 addr | |
| SW1-SW2 | byte (2) | Return code | see list of return codes |
SIGN_RT_ED25519
Command
| Field | Type | Content | Expected |
|---|---|---|---|
| CLA | byte (1) | Application Identifier | 0x05 |
| INS | byte (1) | Instruction ID | 0x05 |
| P1 | byte (1) | Payload desc | 0 = init |
| 1 = add | |||
| 2 = last | |||
| P2 | byte (1) | —- | not used |
| L | byte (1) | Bytes in payload | (depends) |
The first packet/chunk includes only the derivation path.
All other packets/chunks should contain message to sign.
First Packet
| Field | Type | Content | Expected |
|---|---|---|---|
| Path[0] | byte (4) | Derivation Path Data | 44 |
| Path[1] | byte (4) | Derivation Path Data | 474 |
| Path[2] | byte (4) | Derivation Path Data | ? |
| Path[3] | byte (4) | Derivation Path Data | ? |
| Path[4] | byte (4) | Derivation Path Data | ? |
Other Chunks/Packets
| Field | Type | Content | Expected |
|---|---|---|---|
| Data | bytes… | Meta+Message |
Data is defined as:
| Field | Type | Content | Expected |
|---|---|---|---|
| Meta | bytes.. | CBOR metadata | |
| Message | bytes.. | CBOR data to sign |
Response
| Field | Type | Content | Note |
|---|---|---|---|
| SIG | byte (64) | Signature | |
| SW1-SW2 | byte (2) | Return code | see list of return codes |
SIGN_RT_SECP256K1
Command
| Field | Type | Content | Expected |
|---|---|---|---|
| CLA | byte (1) | Application Identifier | 0x05 |
| INS | byte (1) | Instruction ID | 0x07 |
| P1 | byte (1) | Payload desc | 0 = init |
| 1 = add | |||
| 2 = last | |||
| P2 | byte (1) | —- | not used |
| L | byte (1) | Bytes in payload | (depends) |
The first packet/chunk includes only the derivation path.
All other packets/chunks should contain message to sign.
First Packet
| Field | Type | Content | Expected |
|---|---|---|---|
| Path[0] | byte (4) | Derivation Path Data | 44 |
| Path[1] | byte (4) | Derivation Path Data | 60 |
| Path[2] | byte (4) | Derivation Path Data | ? |
| Path[3] | byte (4) | Derivation Path Data | ? |
| Path[4] | byte (4) | Derivation Path Data | ? |
Other Chunks/Packets
| Field | Type | Content | Expected |
|---|---|---|---|
| Data | bytes… | Meta+Message |
Data is defined as:
| Field | Type | Content | Expected |
|---|---|---|---|
| Meta | bytes.. | CBOR metadata | |
| Message | bytes.. | CBOR data to sign |
Response
| Field | Type | Content | Note |
|---|---|---|---|
| SIG | byte (64) | Signature | |
| SW1-SW2 | byte (2) | Return code | see list of return codes |
SIGN_RT_SR25519
Command
| Field | Type | Content | Expected |
|---|---|---|---|
| CLA | byte (1) | Application Identifier | 0x05 |
| INS | byte (1) | Instruction ID | 0x06 |
| P1 | byte (1) | Payload desc | 0 = init |
| 1 = add | |||
| 2 = last | |||
| P2 | byte (1) | —- | not used |
| L | byte (1) | Bytes in payload | (depends) |
The first packet/chunk includes only the derivation path.
All other packets/chunks should contain message to sign.
First Packet
| Field | Type | Content | Expected |
|---|---|---|---|
| Path[0] | byte (4) | Derivation Path Data | 44 |
| Path[1] | byte (4) | Derivation Path Data | 474 |
| Path[2] | byte (4) | Derivation Path Data | ? |
| Path[3] | byte (4) | Derivation Path Data | ? |
| Path[4] | byte (4) | Derivation Path Data | ? |
Other Chunks/Packets
| Field | Type | Content | Expected |
|---|---|---|---|
| Data | bytes… | Meta+Message |
Data is defined as:
| Field | Type | Content | Expected |
|---|---|---|---|
| Meta | bytes.. | CBOR metadata | |
| Message | bytes.. | CBOR data to sign |
Response
| Field | Type | Content | Note |
|---|---|---|---|
| SIG | byte (64) | Signature | |
| SW1-SW2 | byte (2) | Return code | see list of return codes |
Metadata parameter
Meta contains the following fields:
runtime_id: 32-byte runtime IDchain_context: 32-byte chain IDorig_to(optional): 20-byte ethereum destination address
Changes to Allowance transaction
staking.Allow transaction already exists on the consensus layer. We propose the following improvement to the UI:
| Type > | < To > | < Amount > | < Fee > | < Gas limit > | < Network > | < > | < |
| Allowance | <TO> | ROSE +-<AMOUNT> | ROSE <FEE> | <GAS LIMIT> | <NETWORK> | APPROVE | REJECT |
| | | | | | | | |
IMPROVEMENT: The hardware wallet renders the following literals in place of TO for specific NETWORK and addresses:
- Network: Mainnet, To:
oasis1qrnu9yhwzap7rqh6tdcdcpz0zf86hwhycchkhvt8→Cipher - Network: Testnet, To:
oasis1qqdn25n5a2jtet2s5amc7gmchsqqgs4j0qcg5k0t→Cipher - Network: Mainnet, To:
oasis1qzvlg0grjxwgjj58tx2xvmv26era6t2csqn22pte→Emerald - Network: Testnet, To:
oasis1qr629x0tg9gm5fyhedgs9lw5eh3d8ycdnsxf0run→Emerald - Network: Mainnet, To:
oasis1qrd3mnzhhgst26hsp96uf45yhq6zlax0cuzdgcfc→Sapphire - Network: Testnet, To:
oasis1qqczuf3x6glkgjuf0xgtcpjjw95r3crf7y2323xd→Sapphire
For more information on how the addresses above are derived from the runtime ID check the runtime accounts section.
Signing general runtime transactions
Deposit
We propose the following UI for consensus.Deposit runtime transaction:
| Type > | < To (1/1) > | < Amount > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Deposit | <MIXED_TO> | <SYM> <AMOUNT> | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | | | | | |
MIXED_TO can either be oasis1 or the Ethereum’s 0x address. If Meta does not contain orig_to field, render the tx.call.body.to value in oasis1 format in place of MIXED_TO. If Meta.orig_to is set, then:
- Check that the ethereum address stored in
orig_tofield maps to the native address intx.call.body.toaccording to the reference implementation of the mapping. - Render
orig_tovalue in0xformat in place ofMIXED_TO.
In addition, if tx.call.body.to is empty, then the deposit is made to the signer’s account inside the runtime. In this case Self literal is rendered in place of MIXED_TO.
AMOUNT and FEE show the amount of tokens transferred in the transaction and the transaction fee. The number must be formatted according to the number of decimal places and showing a corresponding symbol SYM beside. These are determined by the following mapping hardcoded in the hardware wallet:
(Network, Runtime ID, Denomination) → (Number of decimals, SYM)
Denomination information is stored in tx.part.body.amount[1] or tx.ai.fee.amount[1] for the tokens transferred in the transaction or the fee respectively. Empty Denomination is valid and signifies the native token for the known networks and runtime IDs (see below).
The hardware wallet should have at least the following mappings hardcoded:
- Network: Mainnet, runtime ID: Cipher, Denomination: “” → 9,
ROSE - Network: Testnet, runtime ID: Cipher, Denomination: “” → 9,
TEST - Network: Mainnet, runtime ID: Emerald, Denomination: “” → 18,
ROSE - Network: Testnet, runtime ID: Emerald, Denomination: “” → 18,
TEST - Network: Mainnet, runtime ID: Sapphire, Denomination: “” → 18,
ROSE - Network: Testnet, runtime ID: Sapphire, Denomination: “” → 18,
TEST
If the lookup fails, the following policy should be respected:
SYMis rendered as empty string.- The number of decimals is 18, if runtime ID matches any Emerald or Sapphire runtime on any network.
- Otherwise, the number of decimals is 9.
RUNTIME shows the 32-byte hex encoded runtime ID stored in Meta.runtime_id. If NETWORK matches Mainnet or Testnet, then human-readable version of RUNTIME is shown:
- Network: Mainnet, runtime ID:
000000000000000000000000000000000000000000000000e199119c992377cb→Cipher - Network: Testnet, runtime ID:
0000000000000000000000000000000000000000000000000000000000000000→Cipher - Network: Mainnet, runtime ID:
000000000000000000000000000000000000000000000000e2eaa99fc008f87f→Emerald - Network: Testnet, runtime ID:
00000000000000000000000000000000000000000000000072c8215e60d5bca7→Emerald - Network: Mainnet, runtime ID:
000000000000000000000000000000000000000000000000f80306c9858e7279→Sapphire - Network: Testnet, runtime ID:
000000000000000000000000000000000000000000000000a6d1e3ebf60dff6c→Sapphire
SIGNATURE CONTEXT COMPUTATION: Chain domain separation context for runtime transactions beginning with oasis-runtime-sdk/tx: v0 for chain and followed by the hash derived from Meta.runtime_id and Meta.chain_context. See golang implementation for the reference implementation.
Withdraw
We propose the following UI for consensus.Withdraw method:
| Type > | < To (1/1) > | < Amount > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Withdraw | <TO> | <SYM> <AMOUNT> | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | | | | | |
If tx.call.body.to is empty, then the withdrawal is made to the signer’s consensus account. In this case Self literal is rendered in place of TO.
Transfer
We propose the following UI for the accounts.Transfer method:
| Type > | < To (1/1) > | < Amount > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Transfer | <MIXED_TO> | <SYM> <AMOUNT> | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | | | | | |
Example
The user wants to deposit 100 ROSE to 0xDce075E1C39b1ae0b75D554558b6451A226ffe00 account on Emerald on the Mainnet. First they sign the deposit allowance transaction for Emerald.
| Type > | < To > | < Amount > | < Gas limit > | < Fee > | < Network > | < > | < |
| Allowance | Emerald | ROSE +100.0 | 1277 | ROSE 0.0 | Mainnet | APPROVE | REJECT |
| | Mainnet | | | | | | |
Next, they sign the runtime deposit transaction.
| Type > | < To (1/2) > | < To (2/2) > | < Amount > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Deposit | 0xDce075E1C39b1 | 451A226ffe00 | ROSE 100.0 | ROSE 0.0 | 11310 | Mainnet | Emerald | APPROVE | REJECT |
| (ParaTime) | ae0b75D554558b6 | | | | | | | | |
Then, they transfer some tokens to another account inside the runtime:
| Type > | < To (1/2) > | < To (2/2) > | < Amount > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Transfer | oasis1qpupfu7e2n | m8anj64ytrayne | ROSE 10.0 | ROSE 0.00015 | 11311 | Mainnet | Emerald | APPROVE | REJECT |
| (ParaTime) | 6pkezeaw0yhj8mce | | | | | | | | |
Finally, the user withdraws the remainder of tokens back to the Mainnet.
| Type > | < To (1/2) > | < To (2/2) > | < Amount > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Withdraw | oasis1qrec770vre | 504k68svq7kzve | ROSE 99.9997 | ROSE 0.00015 | 11311 | Mainnet | Emerald | APPROVE | REJECT |
| (ParaTime) | k0a9a5lcrv0zvt22 | | | | | | | | |
Signing smart contract runtime transactions
Uploading smart contract
contracts.Upload method will not be signed by the hardware wallet because the size of the Wasm byte code to sign may easily exceed the maximum size of the available encrypted memory.
Instantiating smart contract
We propose the following UI for the contracts.Instantiate method:
| Review Contract > | < Code ID > | < Amount (1/1) > | < Data (1/1) > | ... | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Instantiation | <CODE ID> | <AMOUNT...> | <DATA> | ... | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | ... | | | | | | |
DATA is a JSON-like representation of tx.call.body.data, if the latter is a CBOR-encoded map. If tx.call.body.data is empty or not present, then Data screen is hidden. If tx.call.body.data is in some other format, require blind signing mode and hide Data screen.
Blind signing means that the user does not see all contract information. In some cases – as is this – not even the amount or the contract address! When signing blindly, it is crucial that the user trusts the client application that it generated a non-malicious transaction!
AMOUNT... is the amount of tokens sent. Contract SDK supports sending multiple tokens at once, each with its own denomination symbol. The hardware wallet should render all of them, one per page. For rendering rules of each AMOUNT consult the runtime deposit behavior.
There can be multiple Data screens Data 1, Data 2, …, Data N for each key in tx.call.body.data map. DATA can be one of the following types:
- string
- number (integer, float)
- array
- map
- boolean
- null
Strings are rendered as UTF-8 strings and the following characters need to be escaped: :, ,, }, ], ….
Numbers are rendered in standard general base-10 encoding. Floats use decimal period and should be rendered with at least one decimal.
For strings and numbers that cannot fit a single page, a pagination is activated.
Boolean and null values are rendered as true, false and null respectively on a single page.
Array and map is rendered in form VAL1,VAL2,... and KEY1:VAL1,KEY1:VAL1,... respectively. For security, the items of the map must be sorted lexicographically by KEY. KEY and VAL can be of any supported type. If it is a map or array it is rendered as {DATA} or [DATA] respectively to avoid disambiguation. Otherwise, it is just DATA.
If the content of an array or a map cannot fit a single page, no pagination is introduced. Instead, the content is trimmed, ellipsis … is appended at the end and the screen becomes confirmable. If the user double-clicks it, a subscreen for item n of an array or a map is shown. There is one subscreen for each item of the array or a map of size N titled Data n.1, Data n.2, …, Data n.N which renders the item n as DATA for an array item or DATA:DATA for a map item:
| Data 1.1 (1/1) > | < Data 1.2 (1/1) | < Data 1.3 (1/1) | ... | < |
| <DATA> | <DATA> | <DATA> | | BACK |
| | | | | |
The recursive approach described above allows user to browse through a complete tree of data stracture (typically a request name along with the arguments) by using ⬅️ and ➡️ buttons, visit a child by double-clicking and returning to a parent node by confirming the BACK screen.
The maximum string length, the length of the array, the depth of a map must have reasonable limits on the hardware wallet. If that limit is exceeded, the hardware wallet displays an error on the initial screen. Then, if the user still wants to sign such a transaction, they need to enable blind signing.
The following UI is shown when blind-signing a non-encrypted transaction due to too complex function parameters.
| Review Contract > | < BLIND > | < Instance ID (1/1) > | < Amount > | < Fee > | < Network > | < ParaTime > | < > | < |
| Instantiation | SIGNING! | <INSTANCE ID> | <SYM> <AMOUNT> | <SYM> <FEE> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | | | | | |
Calling smart contract
The hardware wallet should show details of the runtime transaction to the user, when this is possible. We propose the following UI for the contracts.Call method:
| Review Contract > | < Instance ID > | < Amount (1/1) > | < Data (1/1) > | ... | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Call | <INSTANCE ID> | <AMOUNT...> | <DATA> | ... | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | ... | | | | | | |
The Data screen behavior is the same as for contracts.Instantiate transaction.
Upgrading smart contracts
We propose the following UI for the contracts.Upgrade method:
| Review Contract > | < Instance ID (1/1) > | < Amount (1/1) > | < New Code ID (1/1) > | < Data (1/1) > | ... | < ParaTime > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Upgrade | <INSTANCE ID> | <AMOUNT...> | <CODE_ID> | <DATA> | | <RUNTIME> | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | | | | | | | | | |
The Data screen behavior is the same as for the contract instantiate transaction.
Example
To upload, instantiate and call the hello world example running on Testnet Cipher the user first signs the contract upload transaction with a file-based ed25519 keypair. The user obtains the Code ID 3 for the uploaded contract.
Next, the user instantiates the contract and obtains the Instance ID 2.
| Review Contract > | < Code ID > | < Amount > | < Data > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Instantiation | 3 | ROSE 0.0 | {instantiate:{init | ROSE 0.0 | 1348 | Mainnet | Cipher | APPROVE | REJECT |
| (ParaTime) | | | ial_counter:42}} | | | | | | | |
Finally, they perform a call to say_hello function on a smart contract passing the {"who":"me"} object as a function argument.
| Review Contract > | < Instance ID > | < Amount > | < Data > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Call | 2 | ROSE 0.0 | {say_hello:{who:me | ROSE 0.0 | 1283 | Mainnet | Cipher | APPROVE | REJECT |
| (ParaTime) | | | }} | | | | | | |
For a complete example, the user can provide a more complex object:
{
"who": {
"username": "alice",
"client_secret": "e5868ebb4445fc2ad9f949956c1cb9ddefa0d421",
"last_logins": [1646835046, 1615299046, 1583763046, 1552140646],
"redirect": null
}
}
In this case the hardware wallet renders the following UI.
| Review Contract > | < Instance ID > | < Amount > | < Data > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Call | 2 | ROSE 0.0 | {say_hello:{who:{u | ROSE 0.15 | 1283 | Mainnet | Cipher | APPROVE | REJECT |
| (ParaTime) | | | sername:alice,cli… | | | | | | |
V V
| Data 1 > | < |
| say_hello:{who:{us | BACK |
| ername:alice,clie… | |
V V
| Data 1.1 > | < |
| who:{username:alic | BACK |
| e,client_secret:[… | |
V V
| Data 1.1.1 > | < Data 1.1.2 (1/2) > | < Data 1.1.2 (2/2) > | < Data 1.1.3 > | < Data 1.1.4 > | < |
| username:alice | client_secret:e5868e | 1cb9ddefa0d421 | last_logins:[1646835 | redirect:null | BACK |
| | bb4445fc2ad9f949956c | | 046,1615299046,1583… | | |
V V
| Data 1.1.3.1 > | < Data 1.1.3.2 > | < Data 1.1.3.3 > | < Data 1.1.3.4 | < |
| 1646835046 | 1615299046 | 1583763046 | 1552140646 | BACK |
| | | | | |
Signing EVM runtime transactions
Creating smart contract
evm.Create method will not be managed by the hardware wallet because the size of the EVM byte code may easily exceed the wallet’s encrypted memory size.
Calling smart contract
In contrast to contracts.Call, evm.Call method requires contract ABI and support for RLP decoding in order to extract argument names from tx.call.body.data. This is outside of the scope of this ADR and the blind signing, explicitly allowed by the user, is performed.
We propose the following UI:
| Review Contract > | < BLIND > | < Tx hash (1/1) > | < Address (1/1) > | < Amount > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Call | SIGNING! | <TX_HASH> | <ADDRESS> | <SYM> <AMOUNT> | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | | | | | | | |
TX_HASH is a hex representation of sha256 checksum of tx.call.body.data field.
ADDRESS is a hex-encoded address of the smart contract.
Signing encrypted runtime transactions
Encrypted transactions (tx.call.format == 1) contain call data inside the envelope’s data field encrypted with Deoxys-II ephemeral key and X25519 key derivation.
The hardware wallet is not expected to implement any of these decryption schemes, neither it is safe to share the ephemeral key with anyone. Instead, the user should enable blind signing and the hardware wallet should show the hash of the encrypted call data, the public key and the nonce:
| Review Encrypted > | < BLIND > | < Tx hash (1/1) > | < Public key (1/1) > | < Nonce (1/1) > | < Fee > | < Gas limit > | < Network > | < ParaTime > | < > | < |
| Transaction | SIGNING! | <TX_HASH> | <PUBLIC_KEY> | <NONCE> | <SYM> <FEE> | <GAS LIMIT> | <NETWORK> | <RUNTIME> | APPROVE | REJECT |
| (ParaTime) | | | | | | | | | | |
TX_HASH is a hex representation of sha256 checksum of tx.call.body.data field.
PUBLIC_KEY is a hex representation of the 32-byte tx.call.body.pk field.
NONCE is a hex representation of the 15-byte tx.call.body.nonce field.
Since the transaction stored inside the tx.call.body.data field is encrypted, there is also no way to discriminate between the transactions, for example contracts.Call, contracts.Upgrade or evm.Call.
Consequences
Positive
Users will have a similar experience for signing runtime transactions on any wallet implementing this ADR.
Negative
For some transactions, user will need to trust the client application and use blind signing.
Neutral
Consideration of roothash.SubmitMsg transactions
This ADR does not propose a UI for generic runtime calls (roothash.SubmitMsg, see ADR 11). The proposed design in this ADR assumes a new release of the hardware wallet app each time a new runtime transaction type is introduced.
Signing contract uploads on hardware wallets
In the future perhaps, if only the merkle root hash of the Wasm contract would be contained in the transaction, signing such a contract could be feasible. See how Ethereum 2.x contract deployment is done using this approach.
Consideration of adding From screen
None of the proposed UIs and the existing implementation of signing the consensus transactions on Ledger show who is a signer of the transaction. The signer’s from address can be extracted from tx.ai.si[0].address_spec.signature.<SIGNATURE TYPE> for oasis native address and if the signer wants to show the Ethereum address, Meta.orig_from should be populated and the hardware wallet should verify it before showing the tx.