Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metadata for offline signers #46

Closed
Closed
Changes from 13 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2fbe803
feat: first draft
Slesarew Nov 8, 2023
8d05445
docs: merkle tree construction rule
Slesarew Nov 14, 2023
36eb717
docs: specify minimal sufficient requirements for shortening
Slesarew Nov 14, 2023
acffee2
Update text/0000-metadata-for-offline-signers.md
Slesarew Nov 15, 2023
0f800bf
chore: set RFC index
Slesarew Nov 20, 2023
8870436
docs: apply suggestions from draft discussion
Slesarew Nov 22, 2023
2c006b4
docs: more detailed description of signed extension and version byte
Slesarew Dec 5, 2023
9b2beb2
Apply suggestions from code review
Slesarew Dec 19, 2023
888223a
Update text/0046-metadata-for-offline-signers.md
Slesarew Dec 21, 2023
0bb74b0
Update text/0046-metadata-for-offline-signers.md
Slesarew Dec 21, 2023
c2708a8
Update text/0046-metadata-for-offline-signers.md
Slesarew Dec 21, 2023
4f7d07b
Update text/0046-metadata-for-offline-signers.md
Slesarew Dec 21, 2023
439e119
More details and pseudocode examples (#1)
Slesarew Dec 26, 2023
ab1c0d4
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 18, 2024
4c42ca9
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 18, 2024
613d1bb
revert metadatadescriptor representation to enum
Slesarew Jan 18, 2024
674498d
replace nulls with empty vectors in pseudocode
Slesarew Jan 19, 2024
5ea8c9b
add pseudocode representation of type from `scale-info`
Slesarew Jan 19, 2024
9c4ccb0
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 22, 2024
9418178
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 22, 2024
4f317c5
pseudocode for tree construction
Slesarew Jan 22, 2024
7b472d1
Remove shortening, transmission, and cold verification
Slesarew Jan 22, 2024
4763f2a
mention that type structure matcher particular metadata version
Slesarew Jan 22, 2024
b192d19
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 29, 2024
958e934
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 29, 2024
a2d8791
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 29, 2024
6d9603b
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 29, 2024
10bfa00
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 29, 2024
17b046c
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 29, 2024
21dc94a
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 29, 2024
78b8e67
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 30, 2024
ebfa55f
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 30, 2024
a00ecf7
remove old mentions of scale encoding from metadata descriptor
Slesarew Jan 30, 2024
91e9bdf
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 30, 2024
2de8cd7
Update text/0046-metadata-for-offline-signers.md
Slesarew Jan 30, 2024
6cb7381
docs: modified V15-oriented shortened structure and Basti's suggesti…
Slesarew Feb 5, 2024
601c623
Update text/0046-metadata-for-offline-signers.md
Slesarew Feb 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 274 additions & 0 deletions text/0046-metadata-for-offline-signers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
# RFC-0000: Metadata for offline signers
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

| | |
| --------------- | ------------------------------------------------------------------------------------------- |
| **Start Date** | 2023-10-31 |
| **Description** | Add SignedExtension to check Metadata Root Hash |
| **Authors** | Alzymologist Oy, Zondax LLC, Parity GmbH |
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

## Summary

Add a metadata digest value (33-byte constant within fixed `spec_version`) to Signed Extensions to supplement signer party with proof of correct extrinsic interpretation. The digest value is generated once before release and is well-known and deterministic. The digest mechanism is designed to be modular and flexible. It also supports partial metadata transfer as needed by the signing party's extrinsic decoding mechanism. This considers signing devices potentially limited communication bandwidth and/or memory capacity.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

## Motivation

### Background

While all blockchain systems support (at least in some sense) offline signing used in air-gapped wallets and lightweight embedded devices, only few allow simultaneously complex upgradeable logic and full message decoding on the cold off-line signer side; Substrate is one of these heartening few, and therefore - we should build on this feature to greatly improve transaction security, and thus in general, network resilience.

As a starting point, it is important to recognise that prudence and due care are naturally required. As we build further reliance on this feature we should be very careful to make sure it works correctly every time so as not to create false sense of security.

In order to enable decoding that is small and optimized for chain storage transactions, a metadata entity is used, which is not at all small in itself (on the order of half-MB for most networks). This is a dynamic data chunk which completely describes chain interfaces and properties that could be made into a portable scale-encoded string for any given network version and passed along into an off-chain device to familiarize it with latest network updates. Of course, compromising this metadata anywhere in the path could result in differences between what user sees and signs, thus it is essential that we protect it.

Therefore, we have 2 problems to be solved:

1. Metadata is large, takes long time to be passed into a cold storage device with memory insufficient for its storage; metadata SHOULD be shortened and transmission SHOULD be optimized.
2. Metadata authenticity SHOULD be ensured.

As of now, there is no working solution for (1), as the whole metadata has to be passed to the device. On top of this, the solution for (2) heavily relies on a trusted party managing keys and ensuring metadata is indeed authentic: creating poorly decentralized points of potential failure.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

### Solution requirements

#### Include metadata digest into signature

Some cryptographically strong digest of metadata MAY be included into signable blob. There SHALL NOT be storage overhead for this blob, nor computational overhead, on the node side; thus MUST be a constant within given runtime version, deterministically defined by metadata.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

- Metadata information that could be used in signable extrinsic decoding MAY be included in digest, its inclusion MUST be indicated in signed extensions;
- Digest MUST be deterministic with respect to metadata;
- Digest MUST be cryptographically strong against pre-image, both first and second;
Slesarew marked this conversation as resolved.
Show resolved Hide resolved
- Extra-metadata information necessary for extrinsic decoding and constant within runtime version MUST be included in digest;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't 100% understand this. Do you mean something like:
Additional information (on top of the metadata) that's required to decode extrinsics and runtime constants for a given runtime version must be included in the digest?
But what are those information? Shouldn't metadata itself includes everything?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, metadata does not contain all information. A few extra things like base currency name ("DOT") and base currency value in plancks are often (not always) not included there. This is a known issue that required us to separately transfer metadata and "additional network specifications" to cold wallets in past. Here we just combine those.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If metadata does not contain all the necessary metadata, I will suggest just fix the metadata. I raised a somewhat related issue before paritytech/substrate#3686

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice solution; however, for things like "units" we'll need more robust automation, those are not arbitrary values, but items declared in runtime. We'll need to make them properly coded part of metadata, otherwise parachain upgrading will end up in mess.

I'm all for fixing metadata issues, but I think this should be done one at a time. This particular issue that units are not mentioned anywhere was discussed for a long time and no effective solution was proposed. I think we should move on with this project and then, if there is a proper way to include units in metadata for all chains, we'll keep wasting couple byte in transfer protocol for empty values and then eventually remove this extra information if an upgrade to shortened metadata happens.

- It SHOULD be possible to quickly withdraw offline signing mechanism without access to cold signing devices;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By this you mean that we may change the version to fix a critical bug and we only need to roll this out on chain to fix it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, and all wallets would have to explicitly change this value, no chance of partial compatibility here. There are many strategies for communicating correct version per chain to cold signer if these diverge for some reason, I think it's out of scope here.

- Digest format SHOULD be versioned.
- Work necessary for proving metadata authenticity MAY be omitted at discretion of signer device design (to support automation tools).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what you mean by this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say that the device may choose not to verify the data. It'd be like current situation with PJS extension for example, that gets the metadata and uses it to interpret the extrinsic, without verifying its authenticity (probably because it can't).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's something we have no chance of enforcing here really. So we just state that it is not provable at this part of the architecture and expect the possibility of absence of this check when designing security strategies (for example, this would be required check in app code audits).

Also, tools like staking payout machine do not really need to check any metadata authenticity since they are limited to small number of calls and are often connected to petty cash accounts that are not worth being attacked anyway. We have an expetimental project ourselves where a signer resides on a physical key for physical lock, it does not need metadata verification as it only signs very narrow range of extrinsics (and it would severely degrade performance to do the check as hardware there is really humble); this is alternative - and very centralized in a sense, you just trust specialized firmware developer - approach to the same problem. To support signers like these we need to keep this optional.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the chain doesn't really care if you checked the metadata or not or if you just added some hash that you got from someone. This is just some way to make it able for people to ensure that they got the correct metadata. TLDR, why do we need to list this here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a reminder to everybody that this is a fact - you should not rely on this actually happening. Explicit statement that proving that the check actually happened is not planned at all, just that the tool that could have done it had all required information in genuine form. I think this is important for future security analysts.


#### Reduce metadata size

Metadata should be stripped from parts that are not necessary to parse a signable extrinsic, then it should be separated into a finite set of self-descriptive chunks. Thus, a subset of chunks necessary for signable extrinsic decoding and rendering could be sent, possibly in small portions (ultimately - one at a time), to cold device together with proof.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Metadata should be stripped from parts that are not necessary to parse a signable extrinsic, then it should be separated into a finite set of self-descriptive chunks. Thus, a subset of chunks necessary for signable extrinsic decoding and rendering could be sent, possibly in small portions (ultimately - one at a time), to cold device together with proof.
Metadata should be stripped from parts that are not necessary to parse a signable extrinsic, then it should be separated into a finite set of self-descriptive chunks. Thus, a subset of chunks necessary for signable extrinsic decoding and rendering could be sent, possibly in small portions, to cold device together with proof.

I don't know why "one at a time" is "ultimately" because parallel transmitting could be more efficient when appliable?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be all varieties of options ideally, to fit growing metadata into as small footprint systems as possible. There should also be options to send it in bulk data-efficient format where transmission channel is limiting. This will ensure that we are future-compatible with unpredictably developing hardware (well, and software) systems. Proposed solution solves this challenge.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these chunks really self descriptive? Self descriptive for would be for me something like using json for transferring them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, they are if you accept that under SCALE-encoding nothing is self-descriptive by itself, but it relies on RuntimeMetadataV14 registry type and basic SCALE types.
Both parts should know what Registry and SCALE is.


- Single chunk with proof payload size SHOULD fit within few kB;
- Chunks handling mechanism SHOULD support chunks being sent in any order without memory utilization overhead;
- Unused enum variants MUST be stripped (this has great impact on transmitted metadata size; examples: era enum, enum with all calls for call batching).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean they are stripped, when sending them to the offline signer? Does this mean that each variant is a new chunk?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily. When sending data to the device, only used variants are sent (all in one type). This differs from the proof generation, which is explained afterwards.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manner of these being sent to device is not specified really, they could be sent as separate chunks or as fewer variant enums or even as full enums, why not. What is important, they are indeed separate chunks for proof generation.


## Stakeholders
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

All chain teams are stakeholders, as implementing this feature would require timely effort on their side and would impact compatibility with older tools.

This feature is essential for **all** offline signer tools; many regular signing tools might make use of it. In general, this RFC greatly improves security of any network implementing it, as many governing keys are used with offline signers.

Implementing this RFC would remove requirement to maintain metadata portals manually, as task of metadata verification would be effectively moved to consensus mechanism of the chain.

## Explanation

Detailed description of metadata shortening and digest process is provided in [metadata-shortener](https://github.com/Alzymologist/metadata-shortener) crate (see `cargo doc --open` and examples). Below are presented algorithms of the process.

### Definitions

#### Metadata structure

Metadata in general consists of four sections:

1. Types registry
2. Pallets
3. Extrinsic metadata
4. Runtime type

Of these, only sections 1-3 contain information required for extrinsic decoding. The most important section is (1) Types registry, that is mostly used in extrinsic decoding. It is also the largest part, thus it is modularized for fractional transmission. Part (2) contains runtime version and is otherwise useless for transaction decoding; thus its contents are reduced to this parameter and included into Metadata Descriptor. Part (3) is included into Metadata Descriptor verbatim.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

#### Metadata descriptor

Values for:

1. `u8` metadata shortening protocol version,
2. SCALE-encoded `ExtrinsicMetadata`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is ExtrinsicMetadata?

3. SCALE-encoded `spec_version` `String`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is string, not u32?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we've seen parachains and standalone chains where this was not u32. Those went extinct some time ago though. This is not forbidden anywhere and people are experimenting sometimes, there is no reason to punish them. Or is there? We certainly should change it into u32 (it would be easier and cleaner indeed) if this is planned to be forced, but that has to be documented well in chain developer docs first, which is out of my reach.

4. SCALE-encoded `spec_name` `String`,
5. `u16` base58 prefix,
Slesarew marked this conversation as resolved.
Show resolved Hide resolved
6. `u8` decimals value or `0u8` if no units are defined,
7. SCALE-encoded `tokenSymbol` `String` defined on chain to identify the name of currency (available for example through `system.properties()` RPC call) or empty string if no base units are defined,

```
struct MetadataDescriptor { // really a scale-encoded enum, thus first field is enum value - only 0x01 currently supported.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved
protocol_version: u8,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a second version? Can this not also be handled using the one byte we have in the digest?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK the byte that we have in the digest is the same as this one. We're then 100% sure that both in runtime build and in metadata shortening process (that happen in different envs and different times) the same process was used.

It seems to be the index of the enum actually. Changing the declaration to an enum would simplify the understanding of it.
I'd as well mention that V0 is already burned.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK the byte that we have in the digest is the same as this one. We're then 100% sure that both in runtime build and in metadata shortening process (that happen in different envs and different times) the same process was used.

But we already put this byte into the tx and it will be signed. So, we already ensure this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeh, true... I still see value for the devices parsing the metadata (since it's scale, they need to know the structure). With that being an enum and not a simple struct, we're sure that we are parsing the info correctly (in terms of metadata, not blob), and it also gives us the flexibility to change it in any chain and keep backwards-compatibility as well with the previous versions.
Otherwise, we need the blob to know how to parse the metadata, and we need the metadata to know how to parse the blob. It's kind of a vicious circle.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just one byte, but a byte that makes implementations a bit more ideomatic and helps developers explicitly state which version of shortener protocol they are using at the moment in their code. I think it's worth it. It's not even mandatory to send it every time - signer device could add it itself - if sending one extra byte matters.

extrinsic_metadata: Vec<u8>, // SCALE from `ExtrinsicMetadata
spec_version: Vec<u8>, // SCALE form `String`
spec_name: Vec<u8>, // SCALE from `String`
Slesarew marked this conversation as resolved.
Show resolved Hide resolved
base58_prefix: u16,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These information are already present in the metadata, why do we need them again here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It simplifies a lot of the logic in devices.
Some parachain might not have the exact constant name for this under system pallet and would be much more difficult to check.
Besides that, some of that information comes into a big 200 bytes blob scale encoded (system.version constant).

Slesarew marked this conversation as resolved.
Show resolved Hide resolved
decimals: u8,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about for the chain that does not have native token or multiple native tokens? I guess we can pass 0 and empty vec for symbol but best to be explicit

token_symbol: Vec<u8>, // SCALE from `String`
}
```

constitute metadata descriptor. This is minimal information that is, together with (shortened) types registry, sufficient to decode any signable transaction.

#### Merkle tree
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we need to explain how a merkle tree works nor what the naming of the individual parts is?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just for conventions - what exactly notation would be used further. Not sure it is used much right now, but I don't see how this hurts either. Might help ask questions for reviewers in same definitions to speed up this process a bit.


A **Complete Binary Merkle Tree** (**CBMT**) is proposed as digest structure.

Every node of the proposed tree has a 32-bit value.

A terminal node of the tree we call **leaf**. Its value is input for digest.

The top node of the tree we call **root**.

All node values for non-leave nodes are not terminal are computed through non-commutative **merge** procedure of child nodes.

In CBMT, all layers must be populated, except for the last one, that must have complete filling from the left.

Nodes are numbered top-down and left-to-right starting with 0 at the top of tree.

```
Example 8-node tree

0
/ \
1 2
/ \ / \
3 4 5 6
/ \
7 8

Nodes 4, 5, 6, 7, 8 are leaves
Node 0 is root

```

### General flow

1. The metadata is converted into lean modular form (vector of chunks)
2. A Merkle tree is constructed from the metadata chunks
3. A root of tree (as a left element) is merged with Metadata Descriptor (as a right element)
Slesarew marked this conversation as resolved.
Show resolved Hide resolved
4. Resulting value is a constant to be included in `additionalSigned` to prove that the metadata seen by cold device is genuine
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really part of the stuff you are describing here? I have the impression that you are describing here how to build the hash, not sure we need to explain how to add it to the signed data.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a general flow of the whole construction; the (4) just indicates final destination of result, as mentioned elsewhere (we say that we are adding a constant to additionalSigned and do not give any name to that step; thus here we just repeat those words to connect flow).


### Metadata modularization

1. Types registry is stripped from `docs` fields.
2. Types records are separated into chunks, with enum variants being individual chunks differing by variant index; each chunk consisting of `id` (same as in full metadata registry) and SCALE-encoded 'Type' description (reduced to 1-variant enum for enum variants). Enums with 0 variants are treated as regular types.
3. Chunks are sorted by `id` in ascending order; chunks with same `id` are sorted by enum variant index in ascending order.
Comment on lines +260 to +262
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We clearly should not mention the docs field, as I assume that this isn't present in the "shortened metadata" representation. In general this algorithm does not make that much difference, if you don't know the types.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Types registry comes directly from full, non-shortened metadata, that has all fields, and this (1) is the stage where it loses docs, not earlier. As we describe algorithm here, this is an essential step. The only other way to describe this would be copying all other fields to new structure, but that is just longer description of the same step.

Also, there is ambiguity about docs field missing altogether vs being blank that would probably surface across implementations; this statement should resolve it.


```
types_registry = metadataV14.types
modularized_registry = EmptyVector<id, type>
for (id, type) in types.registry.iterate_enumerate {
type.doc = Null
if (type is ReduceableEnum) { // false for 0-variant enums
for variant in type.variants.iterate {
variant_type = Type {
path: type.path
type_params: Null
type_def: TypeDef::Variant(variants: [variant])
}
modularized_registry.push(id, variant_type)
}
} else {
modularized_registry.push(id, type)
}
}

modularized_registry.sort(|a, b| {
if a.id == b.id { //only possible for variants
a.variant_index > b.variant_index
} else { a.id > b.id }
}
)

```

### Merging protocol

`blake3` transformation of concatenated child nodes (`blake3(left + right)`) as merge procedure;

### Complete Binary Merkle Tree construction protocol

1. Leaves are numbered in ascending order. Leaf index is associated with corresponding chunk.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it the other way around, you associate chunks with leaf indices?

In general I would also by intuition, order all leaves in ascending order. Then I would chunk them into two and hash them. The output of this would follow the same procedure until only one hash (the root hash) is left.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean generally this is the same as you explain below, but yours sounds somewhat more complicated 😅

Maybe we also don't need to explain this, as this is the default way for building a binary merkle tree?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other approaches; this is kind of canonical for CBMT, but there could be implementers that do not know canonical stuff. I know we've independently made exactly same implementation for hot-to-cold proof transfer protocol with @carlosala based on natural order of universe, but I'm afraid here we should be a bit more explicit as canonical here is not the best construction path, just a convenient one. Ordering matters here, and also there is an issue of pairing last odd leaf that could be resolved in more awkward or even slightly less efficient way (making tree incomplete, but I'm not sure it's obvious for everyone).

Also, just following my description verbatim, it's possible to construct the tree without knowing much of background. That should just make implementations easier hopefully. I was thinking about also writing pseudocode representation, but that's probably too much already.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, I can not directly follow you description verbatim because it is not 100% unambiguously.

2. Merge is performed using the leaf with highest index as right and node with second to highest index as left children; result is pushed to the end of nodes queue and leaves are discarded.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved
3. Step (2) is repeated until no leaves or just one leaf remains; in latter case, the last leaf is pushed to the front of the nodes queue.
4. Right node and then left node is popped from the front of the nodes queue and merged; the result is sent to the end of the queue.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you do it for the nodes in different order than for the leaves?

5. Step (4) is repeated until only one node remains; this is tree root.

```
Resulting tree for metadata consisting of 5 nodes (numbered from 0 to 4):

root
/ \
* *
/ \ / \
* 0 1 2
/ \
3 4
```

### Digest

1. Blake3 hash is computed for each chunk of modular short metadata registry.
3. Complete Binary Merkle Tree is constructed as described above.
4. Root hash of this tree (left) is merged with metadata descriptor blake3 hash (right); this is metadata digest.

Version number and corresponding resulting metadata digest MUST be included into Signed Extensions as specified in Chain Verification section below.
Comment on lines +345 to +349
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it should be fine to just say that you use the root hash plus the hash of the metadata descriptor as digest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting removal of line 206? I was kind of afraid this would be a bit difficult to follow if we don't state what's the next step, but yes, we can remove it. Should we?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I meant for this entire section, but I don't remember exactly what I meant 😅


### Shortening

For shortening, an attempt to decode transaction completely using provided metadata is performed with the same algorithm that would be used on the cold side. All chunks are associated with their leaf indices. An example of this protocol is proposed in [metadata-shortener](https://github.com/Alzymologist/metadata-shortener) that is based on [substrate-parser](https://github.com/Alzymologist/substrate-parser) decoding protocol; any decoding protocol could be used here as long as cold signer's design finds it appropriate for given security model.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this is important for the RFC?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean this?

Suggested change
For shortening, an attempt to decode transaction completely using provided metadata is performed with the same algorithm that would be used on the cold side. All chunks are associated with their leaf indices. An example of this protocol is proposed in [metadata-shortener](https://github.com/Alzymologist/metadata-shortener) that is based on [substrate-parser](https://github.com/Alzymologist/substrate-parser) decoding protocol; any decoding protocol could be used here as long as cold signer's design finds it appropriate for given security model.
For shortening, an attempt to decode transaction completely using provided metadata is performed with the same algorithm that would be used on the cold side. All chunks are associated with their leaf indices. Any decoding protocol could be used here as long as cold signer's design finds it appropriate for given security model.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the entire section is not really important for the RFC?


### Transmission

Shortened metadata chunks MAY be trasmitted into cold device together with Merkle proof in its entirety or in parts, depending on memory capabilities of the cold device and it ability to reconstruct larger fraction of tree. This document does not specify the manner of transmission. The order of metadata chunks MAY be arbitrary, the only requirement is that indices of leaf nodes in Merkle tree corresponding to chunks MUST be communicated. Community MAY handle proof format standartization independently.

### Offline verification

The transmitted metadata chunks are hashed together with proof lemmas to obtain root that MAY be transmitted along with the rest of payload. Verification that the root transmitted with message matches with calculated root is optional; the transmitted root SHOULD NOT be used in signature, calculated root MUST be used; however, there is no mechanism to enforce this - it should be done during cold signers code audit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for these.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or are you suggesting removing everything on partial proofs completely? This kind of explains why we cut things this way, although it does seem obvious.

The other reason to keep it here is just suggested security considerations that might be useful for implementors to understand what's happening here. Maybe it's not something RFC should be taking care of indeed.

Please let me know (or just throw in a suggestion to remove these)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I meant to remove this.

I mean you can just describe in the motivation or somewhere why you chunk the stuff. Aka for easier transfer to offline devices.


### Chain verification

The root of metadata computed by cold device MAY be included into Signed Extensions; this way the transaction will pass as valid iff hash of metadata as seen by cold storage device is identical to consensus hash of metadata, ensuring fair signing protocol.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

The Signed Extension representing metadata digest is a single byte representing both digest vaule inclusion and shortening protocol version; this MUST be included in Signed Extensions set. Depending on its value, a digest value is included as `additionalSigned` to signature computation according to following specification:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the entire digest is added to additional signed? Only the hash? The version will already be added to the "signed extension data" that gets signed.

I would also maybe drop additionalSigned and just explain that it gets added to the data that gets hashed as part of the extrinsic or not.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we add 1 byte that is version/inclusion byte, and then we add 32-byte digest to that part of data. Not explaining that it is part of additionalSigned would just add to unmeasurable amount of confusion around this obscure entity. I lost count of times I've been asked how Signed Extensions work and I spent way too much time figuring it out myself first, probably massively pestered you as well, I don't remember. I'm pretty solid that this should stay here if we want to have any hope of more implementations appearing eventually.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant just to drop additionalSigned and describe it in a more human friendly way. Because outside of Substrate it is maybe not called additionalSigned.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would I explain it differently? It's a field that's called that way in metadata and at least in major Rust and JS codebases, if we drop the name or change it nobody would know where this belongs. Things that go into signature and their order are extremely confusing. It's not described anywhere (yet) in full. So if we just say that it is added to data that gets signed, the manner of addition would remain unclear - and I'm not describing this step anywhere else in the document.


| signed extension value | digest value | comment |
|------------------------|----------------|------------------------------------|
| `0x00` | | digest is not included |
| `0x01` | 32-byte digest | this represents protocol version 1 |
| `0x02` - `0xFF` | *reserved* | reserved for future use |

## Drawbacks

### Increased transaction size

A 1-byte increase in transaction size due to signed extension value. Digest is not included in transferred transaction, only in signing process.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do already have a version byte in extrinsic format. maybe we can just use it instead of a new byte?


### Transition overhead

Some slightly out of spec systems might experience breaking changes as new content of signed extensions is added. It is important to note, that there is no real overhead in processing time nor complexity, as the metadata checking mechanism is voluntary. The only drawbacks are expected for tools that do not implement MetadataV14 self-descripting features.
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

## Testing, Security, and Privacy

The metadata shortening protocol should be extensively tested on all available examples of metadata before releasing changes to either metadata or shortener. Careful code review should be performed on shortener implementation code to ensure security. The main metadata tree would inevitably be constructed on runtime build which would also ensure correctness.

To be able to recall shortener protocol in case of vulnerability issues, a version byte is included.

## Performance, Ergonomics, and Compatibility

### Performance

This is negligibly short pessimization during build time on the chain side. Cold wallets performance would improve mostly as metadata validity mechanism that was taking most of effort in cold wallet support would become trivial.

### Ergonomics

The proposal was optimized for cold storage wallets usage with minimal impact on all other parts of the ecosystem

### Compatibility

Proposal in this form is not compatible with older tools that do not implement proper MetadataV14 self-descriptive features; those would have to be upgraded to include a new signed extensions field.

## Prior Art and References

This project was developed upon a Polkadot Treasury grant; relevant development links are located in [metadata-offline-project](https://github.com/Alzymologist/metadata-offline-project) repository.

## Unresolved Questions

2. How would polkadot-js handle the transition?
Slesarew marked this conversation as resolved.
Show resolved Hide resolved
3. Where would non-rust tools like Ledger apps get shortened metadata content?
Slesarew marked this conversation as resolved.
Show resolved Hide resolved

## Future Directions and Related Material

Changes to code of all cold signers to implement this mechanism SHOULD be done when this is enabled; non-cold signers may perform extra metadata check for better security. Ultimately, signing anything without decoding it with verifiable metadata should become discouraged in all situations where a decision-making mechanism is involved (that is, outside of fully automated blind signers like trade bots or staking rewards payout tools).