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
andMeta.orig_to
as Base16. - Change
SIG
inSIGN_RT_SECP256K1
to 65-byte encoded R,S,V format.
- Encode
- 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_context
fromMeta
, - Require
tx.call.format
to be either0
or1
.
- 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 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 parameter
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 intx.call.body.to
according to the reference implementation of the mapping. - Render
orig_to
value in0x
format 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:
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.