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

Extrinsic v5 definition and specification #124

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from 5 commits
Commits
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
156 changes: 156 additions & 0 deletions text/0124-extrinsic-version-5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# RFC-0124: Extrinsic version 5

| | |
| --------------- | ------------------------------------------------------------------------------------------- |
| **Start Date** | 18 October 2024 |
| **Description** | Definition and specification of version 5 extrinsics |
| **Authors** | George Pisaltu |

## Summary

This RFC proposes the definition of version 5 extrinsics along with changes to the specification and encoding from version 4.

## Motivation

[RFC84](https://github.com/polkadot-fellows/RFCs/blob/main/text/0084-general-transaction-extrinsic-format.md) introduced the specification of `General` transactions, a new type of extrinsic besides the `Signed` and `Unsigned` variants available previously in version 4. Additionally, [RFC99](https://github.com/polkadot-fellows/RFCs/blob/main/text/0099-transaction-extension-version.md) introduced versioning of transaction extensions through an extra byte in the extrinsic encoding. Both of these changes require an extrinsic format version bump as both the semantics around extensions as well as the actual encoding of extrinsics need to change to accommodate these new features.

## Stakeholders

- Runtime users
- Runtime devs
- Wallet devs

## Explanation

### Changes to extrinsic authorization

The introduction of `General` transactions allows the authorization of any and all origins through
extensions. This means that, with the appropriate extension, `General` transactions can replicate
the same behavior present-day v4 `Signed` transactions. Specifically for Polkadot chains, an example
implementation for such an extension is
[`VerifySignature`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/verify-signature),
introduced in the Transaction Extension
[PR3685](https://github.com/paritytech/polkadot-sdk/pull/3685). Other extensions can be inserted
into the extension pipeline to authorize different custom origins. Therefore, a `Signed` extrinsic
variant is redundant to a `General` one strictly in terms of user functionality and could eventually
be deprecated and removed.

### Encoding format for version 5

As with version 4, the encoded extrinsic v5 is a SCALE encoded vector of bytes (`u8`), therefore
starting with the encoded length of the following bytes in compact format. The leading byte after
the length determines the version and type of extrinsic, as specified by
[RFC84](https://github.com/polkadot-fellows/RFCs/blob/main/text/0084-general-transaction-extrinsic-format.md).
For reasons mentioned above, this RFC removes the `Signed` variant for v5 extrinsics.

NOTE: For `Bare` extrinsics, the following bytes will just be the encoded call and nothing else.

For `General` transactions, as stated in
[RFC99](https://github.com/polkadot-fellows/RFCs/blob/main/text/0099-transaction-extension-version.md),
an extension version byte must be added in the next extrinsic version. This byte should allow
georgepisaltu marked this conversation as resolved.
Show resolved Hide resolved
runtimes to expose more than one set of extensions which can be used for a transaction. As far as
the v5 extrinsic encoding is concerned, this extension byte should be encoded immediately after the
leading encoding byte. The extension version byte should be included in payloads to be signed by all
extensions configured by runtime devs to ensure a user's extension version choice cannot be altered
by third parties.

After the extension version byte, the extensions will be encoded next, followed by the call itself.

A quick visualization of the encoding:

- `Bare` extrinsics: `(extrinsic_encoded_len, 0b0000_0101, call)`
- `General` transactions: `(extrinsic_encoded_len, , 0b0100_0101, extension_version_byte, extension, call)`
georgepisaltu marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought the component is called extensions?

Suggested change
- `General` transactions: `(extrinsic_encoded_len, , 0b0100_0101, extension_version_byte, extension, call)`
- `General` transactions: `(extrinsic_encoded_len, , 0b0100_0101, extensions_version_byte, extensions, call)`

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The extension itself is usually a tuple of multiple extensions, generally referred to as the extension pipeline. Technically it's only one extension, the TxExtension commonly defined in runtimes, but that is always a tuple of extensions like CheckNonce, CheckWeight, ChargeTransactionPayment etc., so it would be only one extension version, as it is the version of the tuple, but there are multiple extensions in the pipeline.


### Signatures on Polkadot in General transactions

In order to run a transaction with a signed origin in extrinsic version 5, a user must create the
transaction with an instance of at least one extension responsible for authorizing `Signed` origins
with a provided signature. Alternatively, if users want to use some other origin, they should create
the transaction with this particular extension disabled.
Copy link
Contributor

Choose a reason for hiding this comment

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

dumb Q: would it not be possible to use a set of extensions that ultimately behave differently than classic "Signed Origins Extension", but still have one extension responsible for authorizing Signed origins within this set?

Respectively, I'm not sure this "alternative" sentence is correct.

Suggested change
with a provided signature. Alternatively, if users want to use some other origin, they should create
the transaction with this particular extension disabled.
with a provided signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To your question, yes it is entirely possible and, in fact, what I expect to be implemented in most runtimes. The "alternative" wasn't helpful so I removed it.


As stated before, [PR3685](https://github.com/paritytech/polkadot-sdk/pull/3685) comes with a
Transaction Extension which replicates the current `Signed` transactions in v5 extrinsics, namely
[`VerifySignature`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/verify-signature).
I will use this extension as an example on how to replicate current `Signed` transaction
functionality in the new v5 extrinsic format, though the runtime logic is not constrained to this
particular implementation.

This extension leverages the new inherited implication functionality introduced in
`TransactionExtension` and creates a payload to be signed using the data of all extensions after
itself in the extension pipeline. This extension can be configured to accept a `MultiSignature`,
which makes it compatible with all signature types currently used in Polkadot.

In the context of using and extension such as `VerifySignature` to replicate current `Signed`
georgepisaltu marked this conversation as resolved.
Show resolved Hide resolved
transaction functionality, the steps to generate the payload to be signed are:

1. The extension version byte, call, extension and extension implicit should be encoded (by
"extension" and its implicit we mean only the data associated with extensions that follow this
one in the composite extension type);
2. The result of the encoding should then be hashed using the `BLAKE2_256` hasher;
3. The result of the hash should then be signed with the signature type specified in the extension definition.

```rust
// Step 1: encode the bytes
let encoded = (extension_version_byte, call, transaction_extension, transaction_extension_implicit).encode();
// Step 2: hash them
let payload = blake2_256(&encoded[..]);
// Step 3: sign the payload
let signature = keyring.sign(&payload[..]);
```

### Summary of changes in version 5

In order to minimize the number of changes to the extrinsic format version and also to help all
consumers downstream in the transition period between these extrinsic versions, we should:

- Remove the `Signed` variant starting with v5 extrinsics
- Add the `General` variant starting with v5 extrinsics
- Enable runtimes to support both v4 and v5 extrinsics

## Drawbacks

The metadata will have to accommodate two distinct extrinsic format versions at a given point in
time in order to provide the new functionality in a non-breaking way for users and tooling.
Comment on lines +120 to +121
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not a drawback IMO. Metadata v15 should show v4 and metadata v16 and ahead have a vector of extrinsic versions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is something extra to support in the metadata for both the runtime and users, is this not a drawback?

Copy link
Contributor

Choose a reason for hiding this comment

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

it is both a drawback and an improvement - the new metadata support is an improvement, but having to do this enhancement to the metadata is a drawback to this RFC 😛

maybe add another line explicitly calling out that adding this metadata enhancement is ultimately a good thing that should be useful for potential future scenarios too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the explanation.


## Testing, Security, and Privacy

There is no impact on testing, security or privacy.

## Performance, Ergonomics, and Compatibility

This change makes the authorization through signatures configurable by runtime devs in version 5
extrinsics, as opposed to version 4 where the signing payload algorithm and signatures were
hardcoded. This moves the responsibility of ensuring proper authentication through
`TransactionExtension` to the runtime devs, but a sensible default which closely resembles the
present day behavior will be provided in `VerifySignature`.
Comment on lines +133 to +137
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not true. Signature schemes and addresses were configurable by runtime devs through rust generics. For example, Moonbeam uses only ECDSA signatures with EVM-like addresses.
Besides that, I wouldn't mention VerifySignature since it should be RFC-ed anyways.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Your statement about configurable signature schemes is true. However, I still consider my statement to be true because:

  • The signing payload generation algorithm was hardcoded; this is not the case anymore as any extension can take the inherited implication and add or subtract any data to it and mutate it in any way (such as hashing it - or not) before actually creating a signature.
  • There are now multiple ways of ending up with a Signed origin variant, with arbitrary logic in any TransactionExtension being able to authorize that origin; before, a user HAD to provide a transaction signed by a specific account.

All of this static logic is now moved to extensions. The extensions receive the inherited implication, the generation of which is still hardcoded and handled in this RFC, but is not in any way mandatory to be used in any signing scheme.

I'd agree though that the phrasing isn't clear, but I'm not sure how to improve it.

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 the phrasing is fine as it is. The point is to highlight the increase in configurability, which it does.


### Performance

There is no performance impact.

### Ergonomics

Tooling will have to adapt to be able to tell which authorization scheme is used by a particular
transaction by decoding the extension and checking which particular `TransactionExtension` in the pipeline is enabled to do the origin authorization. Previously, this was done by simply checking whether the transaction is signed or unsigned, as there was only one method of authentication.
georgepisaltu marked this conversation as resolved.
Show resolved Hide resolved

### Compatibility

As long as extrinsic version 4 is still exposed in the metadata when version 5 will be introduced,
the changes will not break existing infrastructure. This should give enough time for tooling to
support version 5 and to remove version 4 in the future.

## Prior Art and References

This is a result of the work in [Extrinsic
Horizon](https://github.com/paritytech/polkadot-sdk/issues/2415) and
[RFC99](https://github.com/polkadot-fellows/RFCs/blob/main/text/0099-transaction-extension-version.md).

## Unresolved Questions

None.

## Future Directions and Related Material

Following this change, extrinsic version 5 will be introduced as part of the [Extrinsic
Horizon](https://github.com/paritytech/polkadot-sdk/issues/2415) effort, which will shape future
work.