AEX: 8
Title: Message Signing
Author: Andreas Gassmann (@AndreasGassmann), Alessandro De Carli (@dcale)
License: BSD-3-Clause
Discussions-To: TBD
Status: Draft
Type: Standards Track
Created: 2019-05-13
This document defines a standard how arbitrary messages or payload can be signed using an aeternity keypair.
Message signing can be used in a wide variety of scenarios. Common use cases include:
- Authentication / Login (Proving you are the owner of an address/pubkey)
- Authorization (Giving a user priviliges based on the coins / tokens he owns)
- Prove Data Integrity
- Voting
This technique has been around for a long time, but with the rise in crypto adoption it becomes more accessible to the average user.
- DApp / Website / Wallet prepares a signing request containing a message (challenge) and other parameters (see below)
- Signing request is sent to the Wallet / Signer containing the Private Key
- User can select an identity (= keypair)
- Message will be signed
- User is either being redirected to the provided callback URL, or the signed message is displayed (eg. QR code)
The signature should be encoded in hex
: Buffer.from(signature).toString("hex");
const signingRequest = {
message: string // Message to be signed
publicKey?: string // OPTIONAL: allows wallet to pre-select signing identity
ttl?: string // OPTIONAL: Blockheight or timestamp to prevent replay attacks
origin?: string // OPTIONAL: eg. aeternity.com
callbackURL?: string // OPTIONAL: eg. https://aeternity.com/?signedMessage=
}
const signingResponse = {
message: string // Message to be signed
signature: string[] // Signature of the message
publicKey: string[] // allows wallet to pre-select signing identity
ttl?: string // Blockheight or timestamp to prevent replay attacks
origin?: string // OPTIONAL: eg. aeternity.com
}
The response can contain an array of signatures and publicKeys, which allows a single message to be signed by multiple identities at once.
// TODO: How do we fit TTL and hostname into the message?
import * as sodium from "libsodium-wrappers";
const signMessage = async (
message: string,
privateKey: Buffer
): Promise<string> => {
await sodium.ready;
const signature = sodium.crypto_sign_detached(
sodium.from_string(message),
privateKey
);
const hexSignature = Buffer.from(signature).toString("hex");
return hexSignature;
};
import * as sodium from "libsodium-wrappers";
const verifyMessage = async (
message: string,
hexSignature: string,
publicKey: Buffer
): Promise<boolean> => {
await sodium.ready;
const signature = new Uint8Array(Buffer.from(hexSignature, "hex"));
const isValidSignature = sodium.crypto_sign_verify_detached(
signature,
message,
publicKey
);
return isValidSignature;
};
// risk monitor path sick coconut cube ecology brief table adapt evil oven
const privateKey = Buffer.from(
"7f192bc4b5d6e828b6aeed3958f791f2d4d0f69e9b34a164df41f0f325c48ceb7d29631b2cc36eb4931a2ebe8b0a1bd95a29825ec21430b9e472146c851d2cfd",
"hex"
);
const publicKey = Buffer.from(
"7d29631b2cc36eb4931a2ebe8b0a1bd95a29825ec21430b9e472146c851d2cfd",
"hex"
);
const message = "This message will be signed.";
const signature = await signMessage(message, publicKey);
// e650110d48b42cf07f577b886f852b36945da4b175cb1629528b705799d1565799802c7fbb7d07685f8b22185db7ce5bd03f7e0e754b904b80b4fe4fda4f1802
const isValidSignature = await verifyMessage(message, signature, publicKey);
// true
Serialization and Transport Layer are not part of this propaosal.
The Serialization is being discussed as part of AEX-7 Data Serialization
In order to prevent malicious DApps from sending a seeminly random message to be signed that actually contains data like a valid transaction, we should add a prefix to the message so this can't be exploited.