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:
+
+- Fix Deoxys-II field description in Signing encrypted runtime
+transactions section.
+- Rename
SIGN_PT_
instructions in APDUSPEC to SIGN_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_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
+