Description
Problem Definition
The Cosmos SDK has historically used Amino JSON for signing of transactions whereas Amino binary is used for encoding.
During the SDK's migration to protobuf, we had made the preliminary decision to use a canonical protobuf JSON encoding for signing as described in https://github.com/regen-network/canonical-proto3.
As a consequence of #6030, the Cosmos SDK is moving in the direction of using protobuf's Any
type for the transaction encoding and signing format. In this discussion, a number of participants have asked that we revisit the transaction signing discussion. The options that have been discussed/are available for consideration are outlined below.
It should be noted that it is theoretically possible to support more than one of the following options via an enum flag on the signature. Whether that should or should not be done is a related question.
Proposals
Feel free to suggest updates to these alternatives in the comments
(1) Protobuf canonical JSON
The official proto3 spec defines a canonical mapping to JSON. This is not really deterministic, however, so we define a canonical encoding on top of that using https://gibson042.github.io/canonicaljson-spec/.
Pros:
- By including field names, JSON is more self-describing than proto and is somewhat human-readable
- Can prevent certain user errors made when manually copying proto definitions or if proto definitions are changed between versions (shouldn't be done)
- Relatively easy migration for existing signing tools like the Ledger app
- Causes transaction verifiers to reject unknown fields which is generally correct
Cons:
- Converting to another format for signing may introduce transaction malleability vulnerabilities - i.e when different encoding representations map to a single signing representation. This is known to exist, at least for
Timestamp
, although that malleability is likely un-exploitable. - Requires clients to have proto JSON and canonical JSON in addition to protobuf. There are very few implementations of canonical JSON (only go as far as we know) and some protobuf implementations don't implement proto JSON correctly (none of the Rust ones do for example)
- Doesn't bech32 encode addresses, pubkeys, etc. like Amino JSON
- By default, encodes fields in lowerCamelCase, i.e.
some_field
becomessomeField
- Special treatment for well known types (e.g. Timestamp, Any, Duration, Struct) not necessarily supported well in existing libraries.
- And kind of renaming in field names leads to breaking changes of the message
(2) Protobuf canonical binary
This involves re-encoding the protobuf used for transaction encoding canonically for signing - meaning that fields must be ordered and defaults omitted. This is how Weave does signing.
Pros:
- Simpler for clients to implement, most protobuf implementations serialize things canonically
- Canonicalization could prevent a certain small class of user errors (if the wrong protobuf definition was used to encode a message)
- Renaming fields is a non-breaking change (as long as semantics don't change)
- Causes transaction verifiers to reject unknown fields which is generally correct
Cons:
- Introduces a subtle transaction malleability vulnerability if modules attempt to use proto2 semantics - i.e. interpret null/omitted differently from default/zero
- Not all protobuf implementations serialize things canonically and may require an additional canonicalization layer
- Doesn't prevent user errors made to reordering fields when copying or modifying proto files (like JSON does), but this should be much less of an issue using
Any
(because the full type URL is included andoneof
's are not copied manually) and breaking change checkers like Buf/Prototool - Hard to implement in the Ledger app for many/arbitrary message types
(3) Protobuf binary as encoded in transaction
This simply uses the protobuf encoding as broadcast in the transaction. This becomes a little easier for both signature creation and verification because of Any
(although it could be done without Any too). Because Any
wraps the raw bytes of the sdk.Msg
, it is pretty easy to use these same exact bytes for signing and verification and only require that SignDoc
itself is encoded canonically, rather than every Msg
Pros:
- Simpler for clients to implement than even (2). All implementations should pretty much just work
- Is not vulnerable to proto2 semantics transaction malleability as (2)
Cons:
- Same as (2) in not preventing user errors related to manually copying or tampering with proto definitions, although less of an issue with
Any
- Does not cause transaction verifiers to reject unknown fields (unlike 1, 2 & 4) which may lead to unexpected behavior for comments
(4) Amino JSON
This is how tx's are signed currently. The reason this is under consideration is because breaking Amino JSON signing would break many clients especially the ledger app. Transactions could still be encoded with protobuf and the /tx/encode
endpoint could accept Amino JSON and return protobuf rather than amino binary for tx broadcasting - some upfront work would be required to enable this but it is possible
Pros:
- Doesn't break existing clients right away
- Doesn't introduce new transaction malleability issues that weren't already there
- Bech32 encodes addresses, pubkeys, etc. for better readability than (1)
- Causes transaction verifiers to reject unknown fields which is generally correct
Cons:
- Delays the deployment of protobuf signing, although Amino JSON signing could be enabled via a flag on
Signature
so maybe this is a non-issue - Requires an additional layer on top of protobuf with information that isn't conveyed by the protobuf schema and so far lacks extensive library support
(5) Custom Proto JSON
Extend (1) to support custom encoding of certain types like bech32 addresses
Pros:
- The same benefits as Amino JSON for signing with embedded devices but derived from .proto files, i.e. human readable bech32 addresses and keys
- An easier migration path than (1)
- Does not try to re-use an encoding that was never designed to be deterministic
(multiple signing algorithms?)
Cons:
- Requires a possibly even more complex custom JSON layer on top of protobuf plus extensions to protobuf field to indicate custom formatting like bech32
- Every signature verifier (on and off-chain) needs to maintain support all options (forever)
Related Question: Should we allow multiple signing algorithms?
Theoretically we can allow clients to use multiple signing algorithms and indicate which one they used as an enum flag on the Signature
struct.
Pros:
- allows clients to sign with the method they feel are able to use and feel most secure with
- allows Amino JSON compatibility without delaying protobuf signing support
Cons:
- increases the number of vulnerability paths that need auditing
- increases maintenance requirements for supporting multiple paths* leaves too much responsibility to clients rather than making an opinionated, well-informed decision
For Admin Use
- Not duplicate issue
- Appropriate labels applied
- Appropriate contributors tagged
- Contributor assigned/self-assigned
Activity