mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 16:21:02 +00:00
signed wrapper (#1283)
* add signed wrapper, typedef SignedStatement * typedef SignedAvailabilityBitfield * implement Signed wrapper This is strictly an addition as of this commit; nothing is yet changed in existing behavior. * inline getters, remove review comment * move EncodeAs, Signed from node::primitives to primitives::parachain * Refactor SignedAvailabilityBitfield to use Signed * don't double-encode real payload This isn't an ideal solution, because it depends on the implementation details of how SCALE encodes tuples, but OTOH that behavior seems unlikely to change anytime soon. * fix build errors * cause the runtime to build properly with the new changes Not sure why cargo check didn't catch this earlier; oh well. * fix runtime tests and separate SignedStatement from SignedFullStatement * better explain why CompactStatement exists Co-authored-by: Robert Habermeier <rphmeier@gmail.com> Co-authored-by: Robert Habermeier <rphmeier@gmail.com>
This commit is contained in:
committed by
GitHub
parent
90a1ba1e90
commit
14ce04c9cd
@@ -35,11 +35,11 @@ The Statement Distribution subsystem sends statements to peer nodes and detects
|
||||
|
||||
## Peer Receipt State Machine
|
||||
|
||||
There is a very simple state machine which governs which messages we are willing to receive from peers. Not depicted in the state machine: on initial receipt of any [`SignedStatement`](../../types/backing.md#signed-statement-type), validate that the provided signature does in fact sign the included data. Note that each individual parablock candidate gets its own instance of this state machine; it is perfectly legal to receive a `Valid(X)` before a `Seconded(Y)`, as long as a `Seconded(X)` has been received.
|
||||
There is a very simple state machine which governs which messages we are willing to receive from peers. Not depicted in the state machine: on initial receipt of any [`SignedFullStatement`](../../types/backing.md#signed-statement-type), validate that the provided signature does in fact sign the included data. Note that each individual parablock candidate gets its own instance of this state machine; it is perfectly legal to receive a `Valid(X)` before a `Seconded(Y)`, as long as a `Seconded(X)` has been received.
|
||||
|
||||
A: Initial State. Receive `SignedStatement(Statement::Second)`: extract `Statement`, forward to Candidate Backing, proceed to B. Receive any other `SignedStatement` variant: drop it.
|
||||
A: Initial State. Receive `SignedFullStatement(Statement::Second)`: extract `Statement`, forward to Candidate Backing, proceed to B. Receive any other `SignedFullStatement` variant: drop it.
|
||||
|
||||
B: Receive any `SignedStatement`: check signature, forward to Candidate Backing. Receive `OverseerMessage::StopWork`: proceed to C.
|
||||
B: Receive any `SignedFullStatement`: check signature, forward to Candidate Backing. Receive `OverseerMessage::StopWork`: proceed to C.
|
||||
|
||||
C: Receive any message for this block: drop it.
|
||||
|
||||
|
||||
@@ -5,21 +5,15 @@ candidates for the duration of a challenge period. This is done via an erasure-c
|
||||
|
||||
## Signed Availability Bitfield
|
||||
|
||||
A bitfield signed by a particular validator about the availability of pending candidates.
|
||||
A bitfield [signed](backing.html#signed-wrapper) by a particular validator about the availability of pending candidates.
|
||||
|
||||
|
||||
```rust
|
||||
struct SignedAvailabilityBitfield {
|
||||
validator_index: ValidatorIndex,
|
||||
bitfield: Bitvec,
|
||||
signature: ValidatorSignature,
|
||||
}
|
||||
pub type SignedAvailabilityBitfield = Signed<Bitvec>;
|
||||
|
||||
struct Bitfields(Vec<(SignedAvailabilityBitfield)>), // bitfields sorted by validator index, ascending
|
||||
```
|
||||
|
||||
The signed payload is the SCALE encoding of the tuple `(bitfield, signing_context)` where `signing_context` is a [`SigningContext`](../types/candidate.md#signing-context).
|
||||
|
||||
## Proof-of-Validity
|
||||
|
||||
Often referred to as PoV, this is a type-safe wrapper around bytes (`Vec<u8>`) when referring to data that acts as a stateless-client proof of validity of a candidate, when used as input to the validation function of the para.
|
||||
|
||||
@@ -19,6 +19,35 @@ enum ValidityAttestation {
|
||||
}
|
||||
```
|
||||
|
||||
## Signed Wrapper
|
||||
|
||||
There are a few distinct types which we desire to sign, and validate the signatures of. Instead of duplicating this work, we extract a signed wrapper.
|
||||
|
||||
```rust,ignore
|
||||
/// A signed type which encapsulates the common desire to sign some data and validate a signature.
|
||||
///
|
||||
/// Note that the internal fields are not public; they are all accessable by immutable getters.
|
||||
/// This reduces the chance that they are accidentally mutated, invalidating the signature.
|
||||
struct Signed<Payload, RealPayload=Payload> {
|
||||
/// The payload is part of the signed data. The rest is the signing context,
|
||||
/// which is known both at signing and at validation.
|
||||
payload: Payload,
|
||||
/// The index of the validator signing this statement.
|
||||
validator_index: ValidatorIndex,
|
||||
/// The signature by the validator of the signed payload.
|
||||
signature: ValidatorSignature,
|
||||
}
|
||||
|
||||
impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPayload> {
|
||||
fn sign(payload: Payload, context: SigningContext, index: ValidatorIndex, key: ValidatorPair) -> Signed<Payload, RealPayload> { ... }
|
||||
fn validate(&self, context: SigningContext, key: ValidatorId) -> bool { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Note the presence of the [`SigningContext`](../types/candidate.html#signing-context) in the signatures of the `sign` and `validate` methods. To ensure cryptographic security, the actual signed payload is always the SCALE encoding of `(payload.into(), signing_context)`. Including the signing context prevents replay attacks.
|
||||
|
||||
`EncodeAs` is a helper trait with a blanket impl which ensures that any `T` can `EncodeAs<T>`. Therefore, for the generic case where `RealPayload = Payload`, it changes nothing. However, we `impl EncodeAs<CompactStatement> for Statement`, which helps efficiency.
|
||||
|
||||
## Statement Type
|
||||
|
||||
The [Candidate Backing subsystem](../node/backing/candidate-backing.md) issues and signs these after candidate validation.
|
||||
@@ -38,28 +67,38 @@ enum Statement {
|
||||
/// A statement about the invalidity of a candidate.
|
||||
Invalid(Hash),
|
||||
}
|
||||
```
|
||||
|
||||
## Signed Statement Type
|
||||
|
||||
A statement, the identifier of a validator, and a signature.
|
||||
|
||||
```rust
|
||||
/// A signed statement.
|
||||
struct SignedStatement {
|
||||
/// The index of the validator signing this statement.
|
||||
validator_index: ValidatorIndex,
|
||||
/// The statement itself.
|
||||
statement: Statement,
|
||||
/// The signature by the validator on the signing payload.
|
||||
signature: ValidatorSignature
|
||||
/// A statement about the validity of a parachain candidate.
|
||||
///
|
||||
/// This variant should only be used in the production of `SignedStatement`s. The only difference between
|
||||
/// this enum and `Statement` is that the `Seconded` variant contains a `Hash` instead of a `CandidateReceipt`.
|
||||
/// The rationale behind the difference is that the signature should always be on the hash instead of the
|
||||
/// full data, as this lowers the requirement for checking while retaining necessary cryptographic properties
|
||||
enum CompactStatement {
|
||||
/// A statement about a new candidate being seconded by a validator. This is an implicit validity vote.
|
||||
Seconded(Hash),
|
||||
/// A statement about the validity of a candidate, based on candidate's hash.
|
||||
Valid(Hash),
|
||||
/// A statement about the invalidity of a candidate.
|
||||
Invalid(Hash),
|
||||
}
|
||||
```
|
||||
|
||||
The actual signed payload will be the SCALE encoding of `(compact_statement, signing_context)` where
|
||||
`compact_statement` is a tweak of the [`Statement`](#statement) enum where all variants, including `Seconded`, contain only the hash of the candidate, and the `signing_context` is a [`SigningContext`](../types/candidate.md#signing-context).
|
||||
`CompactStatement` exists because a `CandidateReceipt` includes `HeadData`, which does not have a bounded size.
|
||||
|
||||
This prevents against replay attacks and allows the candidate receipt itself to be omitted when checking a signature on a `Seconded` statement in situations where the hash is known.
|
||||
## Signed Statement Type
|
||||
|
||||
A statement which has been [cryptographically signed](#signed-wrapper) by a validator.
|
||||
|
||||
```rust
|
||||
/// A signed statement, containing the abridged candidate receipt in the `Seconded` variant.
|
||||
pub type SignedFullStatement = Signed<Statement, CompactStatement>;
|
||||
|
||||
/// A signed statement, containing only the hash.
|
||||
pub type SignedStatement = Signed<CompactStatement>;
|
||||
```
|
||||
|
||||
Munging the signed `Statement` into a `CompactStatement` before signing allows the candidate receipt itself to be omitted when checking a signature on a `Seconded` statement.
|
||||
|
||||
## Backed Candidate
|
||||
|
||||
|
||||
@@ -158,11 +158,11 @@ enum MisbehaviorReport {
|
||||
/// this message should be dispatched with all of them, in arbitrary order.
|
||||
///
|
||||
/// This variant is also used when our own validity checks disagree with others'.
|
||||
CandidateValidityDisagreement(CandidateReceipt, Vec<SignedStatement>),
|
||||
CandidateValidityDisagreement(CandidateReceipt, Vec<SignedFullStatement>),
|
||||
/// I've noticed a peer contradicting itself about a particular candidate
|
||||
SelfContradiction(CandidateReceipt, SignedStatement, SignedStatement),
|
||||
SelfContradiction(CandidateReceipt, SignedFullStatement, SignedFullStatement),
|
||||
/// This peer has seconded more than one parachain candidate for this relay parent head
|
||||
DoubleVote(CandidateReceipt, SignedStatement, SignedStatement),
|
||||
DoubleVote(CandidateReceipt, SignedFullStatement, SignedFullStatement),
|
||||
}
|
||||
```
|
||||
|
||||
@@ -227,7 +227,7 @@ enum RuntimeApiMessage {
|
||||
|
||||
## Statement Distribution Message
|
||||
|
||||
The Statement Distribution subsystem distributes signed statements from validators to other validators.
|
||||
The Statement Distribution subsystem distributes signed statements and candidates from validators to other validators. It does this by distributing full statements, which embed the candidate receipt, as opposed to compact statements which don't.
|
||||
It receives updates from the network bridge and signed statements to share with other validators.
|
||||
|
||||
```rust
|
||||
@@ -239,7 +239,7 @@ enum StatementDistributionMessage {
|
||||
///
|
||||
/// The statement distribution subsystem assumes that the statement should be correctly
|
||||
/// signed.
|
||||
Share(Hash, SignedStatement),
|
||||
Share(Hash, SignedFullStatement),
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user