Simply Cosmos signed message spec (using TM canonical JSON)
This commit is contained in:
parent
9cfe2c59f1
commit
aa01e9954c
|
@ -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-712<sup>1</sup>
|
||||
and in general Ethereum's `eth_sign` and `eth_signTypedData` methods<sup>2</sup>.
|
||||
|
||||
### 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)` = <code>sha256(value<sub>1</sub>) || sha256(value<sub>2</sub>) || ... || sha256(value<sub>n</sub>)`</code>, where <code>value<sub>1</sub> < value<sub>2</sub></code>
|
||||
|
||||
### 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`
|
||||
|
||||
<hr>
|
||||
|
||||
**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`
|
||||
|
||||
<hr>
|
||||
|
||||
**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
|
||||
|
|
Loading…
Reference in New Issue