- Author: anon-br
- Status: Proposed
- Created: 18-March-2022
- Last edited: 18-March-2022
- License: CC0
- Forking: not needed
This document proposes a standard for message signing, verification, and representation.
Arbitrary data signing and verifying can cover use cases such as:
- Proving the ownership of a public key;
- Website access control;
- Document and data stamping.
R1
- Secure: signing process must prevent known attack vectors;R2
- Verifiable: messages must have verifiable encoded representations;R3
- Hardware wallet-friendly: message representation must be small enough to fit well in hardware wallet devices' screens, and the signing process must be as resource-efficient as possible.
To meet requirements R2
, an encoding scheme becomes necessary. This document proposes an extension of the current Ergo addresses codeset.
Currently, we have the following setup:
1
:P2PK
- Pay to Public Key2
:P2SH
- Pay to Script Hash3
:P2S
- Pay to Script
This document proposes a new address type code:
4
:ADH
- Arbitrary Data Hash
Using the hashed data instead of the actual data bytes allows for shorter text representations and meets requirement R3
for representation shortness and resource efficiency.
Message encoding follows the same steps as regular addresses encoding, it mush have a head byte, a body and a checksum.
const addressCode = 4; // 4 = ADH - Arbitrary Data Hash
const headByte = [Network.Mainnet + addressCode];
const dataHash = blake2b256(dataBytes);
const body = concat(headByte, dataHash);
const checksum = blake2b256(body).subarray(0, 4);
const encodedRepresentation = base58(concat(body, checksum));
The data signing process is done similarly to a regular transaction signing, basically a prover takes the data bytes and uses a private key to sign it. But sending data directly to the prover can lead to some known security issues, namely:
AV1
: A bad actor can steal funds by tricking the user and sending transaction bytes in place of data bytes and get a valid signed transaction which will be accepted by the blockchain.AV2
- Repay attacks: A bad actor can ask a user to sign a message on the testnet and reuse the same signature on the mainnet.
To prevent AV1
, this document proposes two guardrails on signing process: 1) prover must hash the data bytes and use the output to sign/verify instead of using data bytes directly (which will also help with R3
), and 2) prefix the hash to be signed/verified with a 0x0
byte.
To avoid AV2
, the network type code should be included after the 0x0
byte.
const bytes = concat(
0x0, // transaction invalidator byte
Network.Mainnet, // network type
blake2b256(dataBytes) // hashed data bytes
);
const signatureBytes = sign(bytes, privateKey);
In the current transaction binary serialization format, every transaction should start with a VLQ
1 value representing the number of inputs a given transaction is spending. Knowing that it's impossible to spend a transaction without inputs, it's safe to assume that a valid serialized transaction will never start with 0x0
, so this will invalidate any transaction bytes that might eventually be sent to be signed as data. Something similar happens on Ethereum, where every message to be signed must be prefixed with 0x19
2.
Footnotes
-
Variable-length quantity: https://en.wikipedia.org/wiki/Variable-length_quantity ↩
-
Ethereum EIP-191 specification: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-191.md#specification ↩