From aa01e9954c4d4936c2b02cbb45dcd81ed00408ff Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 9 Aug 2018 11:35:22 -0400 Subject: [PATCH] Simply Cosmos signed message spec (using TM canonical JSON) --- docs/ics/ics-xxx-signed-messages.md | 266 ++++++++-------------------- 1 file changed, 72 insertions(+), 194 deletions(-) diff --git a/docs/ics/ics-xxx-signed-messages.md b/docs/ics/ics-xxx-signed-messages.md index 58776c31d..50dd67c75 100644 --- a/docs/ics/ics-xxx-signed-messages.md +++ b/docs/ics/ics-xxx-signed-messages.md @@ -2,16 +2,11 @@ >TODO: Replace with valid ICS number and possibly move to new location. - * [Changelog](#changelog) + * [Changelog](#changelog) * [Abstract](#abstract) + * [Preliminary](#preliminary) * [Specification](#specification) - + [Preliminary](#preliminary) - + [Encoding](#encoding) - - [Schema](#schema) - - [encodeStruct](#encodestruct) - - [encodeSchema](#encodeschema) - - [encodeData](#encodedata) - + [DomainSeparator](#domainseparator) + * [Future Adaptations](#future-adaptations) * [API](#api) * [References](#references) @@ -32,7 +27,7 @@ A standardized protocol for hashing, signing, and verifying messages that can be implemented by the Cosmos SDK and other third-party organizations is needed. Such a standardized protocol subscribes to the following: -* Contains a specification of machine-verifiable and human-readable typed structured data +* Contains a specification of human-readable and machine-verifiable typed structured data * Contains a framework for deterministic and injective encoding of structured data * Utilizes cryptographic secure hashing and signing algorithms * A framework for supporting extensions and domain separation @@ -46,13 +41,7 @@ implementation. If you view signed messages in the means of authorizing some action or data, then such an application would have to either treat this as idempotent or have mechanisms in place to reject known signed messages. -## Specification - -> The proposed implementation is motivated and borrows heavily from EIP-7121 -and in general Ethereum's `eth_sign` and `eth_signTypedData` methods2. - -### Preliminary - +## Preliminary The Cosmos message signing protocol will be parameterized with a cryptographic secure hashing algorithm `SHA-256` and a signing algorithm `S` that contains the operations `sign` and `verify` which provide a digital signature over a set @@ -64,186 +53,101 @@ used in Tendermint and the Cosmos SDK and that they satisfy our needs for such cryptographic algorithms such as having resistance to collision and second pre-image attacks, as well as being deterministic and uniform. -### Encoding +## Specification -Our goal is to create a deterministic, injective, machine-verifiable means of -encoding human-readable typed and structured data. +Tendermint has a well established protocol for signing messages using a canonical +JSON representation as defined [here](https://github.com/tendermint/tendermint/blob/master/types/canonical_json.go). -Let us consider the set of signed messages to be: `B ∪ TS`, where `B` is the set -of byte arrays and `TS` is the set of human-readable typed structures. Thus, the -set can can be encoded in a deterministic and injective way via the following -rules, where `||` denotes concatenation: +An example of such a canonical JSON structure is Tendermint's vote structure: -* `encode(b : B)` = `0x0000 || bytes("Signed Cosmos SDK Message: \n") || l || b`, where - * `b`: the bytes to be signed - * `l`: little endian uint64 encoding of the length of `b` -* `encode(ds : TS, ts : TS)` = `0x000001 || encodeStruct(ds) || encodeStruct(ts)`, where - * `ds`: the application domain separator which is also a human-readable typed structure ([see below](#domainseparator)) - * `ts`: the human-readable typed structure to be signed +```golang +type CanonicalJSONVote struct { + ChainID string `json:"@chain_id"` + Type string `json:"@type"` + BlockID CanonicalJSONBlockID `json:"block_id"` + Height int64 `json:"height"` + Round int `json:"round"` + Timestamp string `json:"timestamp"` + VoteType byte `json:"type"` +} +``` -The prefix bytes disambiguate the encoding cases from one another as well as -separating them from collision of transactions to be signed. The `amino` -serialization protocol escapes the set of disambiguation and prefix bytes with a -single `0x00` byte so there should be no collision with those structures. +With such canonical JSON structures, the specification requires that they include +meta fields: `@chain_id` and `@type`. These meta fields are reserved and must be +included. They are both of type `string`. In addition, fields must be ordered +in lexicographically ascending order. -#### Schema +For the purposes of signing Cosmos messages, the `@chain_id` field must correspond +to the Cosmos chain identifier. The user-agent should **refuse** signing if the +`@chain_id` field does not match the currently active chain! The `@type` field +must equal the constant `"message"`. The `@type` field corresponds to the type of +structure the user will be signing in an application. For now, a user is only +allowed to sign bytes of valid ASCII text ([see here](https://github.com/tendermint/tendermint/blob/master/libs/common/string.go#L61-L74)). +However, this will change and evolve to support additional application-specific +structures that are human-readable and machine-verifiable ([see Future Adaptations](#futureadaptations)). -To achieve deterministic and injective encoding, Cosmos signed messages over -type structures will use an existing known standard -- [JSON schema](http://json-schema.org/). -The domain separator and typed structures to be encoded must be specified with -a schema adhering to the JSON schema [specification](http://json-schema.org/specification.html). +Thus, we can have a canonical JSON structure for signing Cosmos messages using +the [JSON schema](http://json-schema.org/) specification as such: ```json { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", "$id": "cosmos/signing/typeData/schema", "title": "The Cosmos signed message typed data schema.", "type": "object", - "definitions": { - "typeDef": { - "type": "array", - "items": { - "description": "The list of properties a schema definition contains.", - "type": "object", - "properties": { - "name": { - "description": "The name of the schema rule.", - "type": "string", - "minLength": 1 - }, - "description": { - "description": "The description of the schema rule.", - "type": "string" - }, - "type": { - "description": "The type of the schema rule.", - "type": "string", - "minLength": 1 - } - }, - "required": [ - "name", - "type" - ] - } - } - }, "properties": { - "types": { - "type": "object", - "properties": { - "CosmosDomain": { - "description": "The application domain separator schema.", - "$ref": "#/definitions/typeDef" - } - }, - "additionalProperties": { - "description": "The application type schemas.", - "$ref": "#/definitions/typeDef" - }, - "required": [ - "CosmosDomain" - ] - }, - "primaryType": { - "description": "The name of primary message type to sign. This is needed for when there is more than one type defined besides `CosmosDomain`.", + "@chain_id": { "type": "string", + "description": "The corresponding Cosmos chain identifier.", "minLength": 1 }, - "domain": { - "description": "The domain separator to sign.", - "type": "object" + "@type": { + "type": "string", + "description": "The message type. It must be 'message'.", + "enum": [ + "message" + ] }, - "message": { - "description": "The message data to sign.", - "type": "object" + "text": { + "type": "string", + "description": "The valid ASCII text to sign.", + "pattern": "^[\\x20-\\x7E]+$", + "minLength": 1 } }, "required": [ - "types", - "primaryType", - "domain", - "message" + "@chain_id", + "@type", + "text" ] } ``` -#### encodeStruct - -The specification of encoding a human-readable typed structures, which includes -the domain separator, is as follows, where `||` denotes concatenation: - -`encodeStruct(ts : TS)` = `sha256(sha256(encodeSchema(ts)) || sha256(encodeData(ts)))` - -**Note**: The typed structure `ts` should include the JSON instance and schema. - -#### encodeSchema - -The **schema** of a typed structure is encoded as the name of the type and the -concatenation of it's schema fields. If the schema internally references other -schemas, then those are appended to the encoding. In order to make the encoding -deterministic, the encoding should sort types by their type name in lexicographic -ascending order. The specification is as follows, where `||` denotes concatenation: - -`encodeSchema(ts : TS)` = `"typeName(" || name || " " || type || ", " || ... || ")"` - e.g. -```json -"Address(address string)Coin(amount integer, denom string)Transaction(coin Coin, from Address, to Address)" -``` - -##### Alternatives - -1. Instead of concatenating schema signatures, we could also take each type -definition and insert them into a sorted JSON object and hash the byte -representation of that object. This would avoid having to perform weird -concatenations. - -#### encodeData - -The **data** of a typed structure is encoded as the concatenation of values in -the typed data sorted by the field names in lexicographic ascending order. The -specification is as follows, where `||` denotes concatenation: - -`encodeData(ts : TS)` = sha256(value1) || sha256(value2) || ... || sha256(valuen)`, where value1 < value2 - -### DomainSeparator - -Encoding structures can still lead to potential collisions and while this may be -okay or even desired, it introduces a concern in that it could lead to two compatible -signatures. The domain separator prevents collisions of otherwise identical -structures. It is designed to be unique per application use and is directly used -in the signature encoding itself. The domain separator is also extensible where -the protocol and application designer may introduce or omit fields to their needs, -but we will provide a "standard" structure that can be used for proper separation -of concerns: - ```json { - "types": { - "CosmosDomain": [ - { - "name": "name", - "description": "The name of the signing origin or application.", - "type": "string" - }, - { - "name": "chainID", - "description": "The corresponding Cosmos chain identifier.", - "type": "string", - }, - { - "name": "version", - "description": "The major version of the domain separator.", - "type": "integer", - }, - ], - }, + "@chain_id": "1", + "@type": "message", + "text": "Hello, you can identify me as XYZ on keybase." } ``` -**Note**: The user-agent should refuse signing if the `chainID` does not match -the currently active chain! +## Future Adaptations + +As applications can vary greatly in domain, it will be vital to support both +domain separation and human-readable and machine-verifiable structures. + +Domain separation will allow for application developers to prevent collisions of +otherwise identical structures. It should be designed to be unique per application +use and should directly be used in the signature encoding itself. + +Human-readable and machine-verifiable structures will allow end users to sign +more complex structures, apart from just string messages, and still be able to +know exactly what they are signing (opposed to signing a bunch of arbitrary bytes). + +Thus, in the future, the Cosmos signing message specification will be expected +to expand upon it's canonical JSON structure to include such functionality. + ## API @@ -255,7 +159,7 @@ adhere to the following specification: **cosmosSignBytes** Params: -* `data`: arbitrary byte length data to sign +* `data`: the Cosmos signed message canonical JSON structure * `address`: 20 byte account address to sign data with Returns: @@ -266,30 +170,7 @@ Returns: **cosmosSignBytesPass** Params: -* `data`: arbitrary byte length data to sign -* `address`: 20 byte account address to sign data with -* `password`: password of the account to sign data with - -Returns: -* `signature`: the Cosmos signature derived using signing algorithm `S` - -
- -**cosmosSignTyped** - -Params: -* `typedData`: type typed data structure, including the domain separator, to encode and sign -* `address`: 20 byte account address to sign data with - -Returns: -* `signature`: the Cosmos signature derived using signing algorithm `S` - -
- -**cosmosSignTypedPass** - -Params: -* `typedData`: type typed data structure, including the domain separator, to encode and sign +* `data`: the Cosmos signed message canonical JSON structure * `address`: 20 byte account address to sign data with * `password`: password of the account to sign data with @@ -297,6 +178,3 @@ Returns: * `signature`: the Cosmos signature derived using signing algorithm `S` ## References - -1. https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md -2. https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign