maciejhirsz / uos

Universal Offline Signatures
52 stars 5 forks source link

Universal Offline Signatures

THIS IS A DRAFT

This document proposes a standard for QR code encoding that enables two-way communication between a Hot Wallet and a Cold Signer with access to private keys, for the purpose of securely signing and broadcasting transactions and data on current and future decentralized networks (including non-Ethereum networks). The goal is have a single, inter-operable standard, allowing users to use any combination of a Hot Wallet and a Cold Signer (defined below) without vendor locking.

Design principles

QR code encoding

The common ways to encode binary data in a QR code would include:

For data density and simplicity this standard will only use the native Binary QR encoding.

Note: Base64 US-ASCII representation with Alphanumeric QR encoding is impossible, as Alphanumeric QR code only permits 44 (5½ bits per character) out of the required 64 characters (6 bits per character).

Nomenclature

Since this technology requires two separate devices/applications, to avoid confusion the following names will be used to differentiate the two:

For describing binary data this standard uses either a single byte index [n], an open left-inclusive range [n..], or a closed left-incluse right-exclusive range [n..m]. [..n] is a shorthand for [0..n]. Examples:

For byte values this standard uses either a single hexadecimal value AA, or a range AA...BB, which is left and right inclusive:

Additionally we will define the following terms to mean:

Steps

Since this is a multi-step process, we will differentiate between the following types of QR codes:

Step Name Direction Contains QR Encoding
0⁽¹⁾ Introduction Cold ⇒ Hot Network identification and Address Binary (UTF-8)
1 Payload Cold ⇐ Hot Data to sign prefixed with metadata Binary
2 Signature Cold ⇒ Hot Signature for Payload Binary

Introduction Step

The goal of this step is for Cold Signer to inform the Hot Wallet about a single account it has access to. To make this useful outside of the scope of this specification, this standard proposes using URI format compatible with EIP-681 and EIP-831, with syntax:

introduction    = scheme ":" details
scheme          = STRING
details         = STRING

Ethereum Introduction

details = address | address "@" chainid | address "@" chainid ":" name

A correct Introduction for address zero (0x0000000000000000000000000000000000000000) on Ethereum is therefore a string:

ethereum:0x0000000000000000000000000000000000000000@1

Substrate Introduction

details = address | address ":" genesishash | address ":" genesishash ":" name

A correct Introduction for address 5GKhfyctwmW5LQdGaHTyU9qq2yDtggdJo719bj5ZUxnVGtmX on a Substrate-based network is therefore a string:

substrate:5GKhfyctwmW5LQdGaHTyU9qq2yDtggdJo719bj5ZUxnVGtmX

Payload Step.

Payload is always read left-to-right, using prefixing to determine how it needs to be read. The first prefix is single byte at index 0:

[0] [1..]
00 Multipart Payload
01...44 Extension range for other networks
45 Ethereum Payload
46...52 Extension range for other networks
53 Substrate Payload
54...7A Extension range for other networks
7B Legacy Ethereum Payload
7C...7F Extension range for other networks
80...FF Reserved

Multipart Payload

QR codes can only represent 2953 bytes, which is a harsh constraint as some transactions, such as contract deployment, may not fit into a single code. Multipart Payload is a way to represent a single Payload as a series of QR codes. Each QR code in Multipart Payload, or a frame, looks as follows:

[0] [1..3] [3..5] [5..]
00 frame frame_count part_data

Once all frames are combined, the part_data must be concatenated into a single binary blob, and then interpreted as a completely new albeit larger Payload, starting from the prefix table above.

Ethereum Payload

Byte 45 is the US-ASCII byte representing the capital letter E. Ethereum Payload follows the table:

Action [0] [1] [2..22] [22..]
Sign a hash 45 00 address hash
Sign a transaction 45 01 address rlp
Sign a message 45 02 address message

TODO: Handle EIP-712 typed data.

Substrate Payload

Byte 53 is the US-ASCII byte representing the capital letter S. Substrate Payload follows the table:

Action [0] [1] [2] [1..1+L] [1+L..]
Sign a transaction 53 crypto 00 accountid payload
Sign a transaction 53 crypto 01 accountid payload_hash
Sign an immortal transaction 53 crypto 02 accountid immortal_payload
Sign a message 53 crypto 03 accountid message

Legacy Ethereum Payload

Byte 7B is the US-ASCII byte representing open curly brace {, for that reason it's treated as a prefix for older, deprecated format. This Payload should be decoded in full as UTF-8 encoded JSON, following either of the two variants:

{
  "action": "signTransaction",
  "data": {
    "account": ADDRESS,
    "rlp": RLP
  }
}

or

{
  "action": "signData",
  "data":{
    "account": ADDRESS,
    "data": MESSAGE
  }
}

Signature Step

Signatures will vary on type of payload that is being signed.

Ethereum Signature

Ethereum signature must follow one of the two following formats:

[0] [1..33] [33..65] [66]
01 r s v

or

[0..64] [64..128] [128..130]
HEX_R HEX_S HEX_V

Pseudocode for folding in CHAIN_ID into v:

if chainId > 0 {
    v += (chainId * 2 + 8) & 0xFF;
}

Substrate Signature

TODO

Copyright

Copyright and related rights waived via CC0.