ADR 0014: Signing Runtime Transactions with Hardware Wallet
Component
Oasis SDK
Changelog
- 2023-02-24:
- APDU: Define Oasis native and Ethereum-compatible address length.
- 2023-02-09:
- Encode
Meta.runtime_id
and Meta.orig_to
as Base16. - Change
SIG
in SIGN_RT_SECP256K1
to 65-byte encoded R,S,V format.
- 2023-01-23:
- 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_context
from Meta
, - Require
tx.call.format
to be either 0
or 1
.
- 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.
The format of the runtime transaction to be signed by the
+hardware wallet is the following:
#[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:
#[derive(Clone, Debug, cbor::Encode, cbor::Decode)]
pub struct Call {
#[cbor(optional, default)]
pub format: CallFormat,
#[cbor(optional, default, skip_serializing_if = "String::is_empty")]
pub method: String,
pub body: cbor::Value,
#[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 format
+error 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 (40) | Lower-case 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 (46) | Bech32 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 (65) | Signature | R,S,V bigendian integers |
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 |
Meta
is a CBOR-encoded string → string map with the following fields:
runtime_id
: Base16-encoded runtime ID (64-byte string)chain_context
: chain ID (64-byte string)orig_to
(optional): Base16-encoded ethereum destination address (40-byte
+string)
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_to
field maps to the
+native address in tx.call.body.to
according to the reference
+implementation of the mapping. - Render
orig_to
value in 0x
format in place of MIXED_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:
SYM
is 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.
References