Unverified Commit 4f79b770 authored by Peter Goodspeed-Niklaus's avatar Peter Goodspeed-Niklaus Committed by GitHub
Browse files

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: asynchronous rob's avatarRobert Habermeier <rphmeier@gmail.com>

Co-authored-by: asynchronous rob's avatarRobert Habermeier <rphmeier@gmail.com>
parent ff708f3a
Pipeline #97758 passed with stages
in 22 minutes and 43 seconds
...@@ -31,7 +31,7 @@ use polkadot_primitives::parachain::{ ...@@ -31,7 +31,7 @@ use polkadot_primitives::parachain::{
SignedAvailabilityBitfield, SigningContext, ValidatorId, ValidationCode, ValidatorIndex, SignedAvailabilityBitfield, SigningContext, ValidatorId, ValidationCode, ValidatorIndex,
}; };
use polkadot_node_primitives::{ use polkadot_node_primitives::{
MisbehaviorReport, SignedStatement, MisbehaviorReport, SignedFullStatement,
}; };
/// Signals sent by an overseer to a subsystem. /// Signals sent by an overseer to a subsystem.
...@@ -68,7 +68,7 @@ pub enum CandidateBackingMessage { ...@@ -68,7 +68,7 @@ pub enum CandidateBackingMessage {
Second(Hash, AbridgedCandidateReceipt), Second(Hash, AbridgedCandidateReceipt),
/// Note a validator's statement about a particular candidate. Disagreements about validity must be escalated /// Note a validator's statement about a particular candidate. Disagreements about validity must be escalated
/// to a broader check by Misbehavior Arbitration. Agreements are simply tallied until a quorum is reached. /// to a broader check by Misbehavior Arbitration. Agreements are simply tallied until a quorum is reached.
Statement(Hash, SignedStatement), Statement(Hash, SignedFullStatement),
} }
/// Blanket error for validation failing. /// Blanket error for validation failing.
...@@ -180,7 +180,7 @@ pub enum RuntimeApiMessage { ...@@ -180,7 +180,7 @@ pub enum RuntimeApiMessage {
pub enum StatementDistributionMessage { pub enum StatementDistributionMessage {
/// We have originated a signed statement in the context of /// We have originated a signed statement in the context of
/// given relay-parent hash and it should be distributed to other validators. /// given relay-parent hash and it should be distributed to other validators.
Share(Hash, SignedStatement), Share(Hash, SignedFullStatement),
} }
/// This data becomes intrinsics or extrinsics which should be included in a future relay chain block. /// This data becomes intrinsics or extrinsics which should be included in a future relay chain block.
......
...@@ -20,13 +20,13 @@ ...@@ -20,13 +20,13 @@
//! not shared between the node and the runtime. This crate builds on top of the primitives defined //! not shared between the node and the runtime. This crate builds on top of the primitives defined
//! there. //! there.
use runtime_primitives::traits::AppVerify; use parity_scale_codec::{Decode, Encode};
use polkadot_primitives::Hash; use polkadot_primitives::{Hash,
use polkadot_primitives::parachain::{ parachain::{
AbridgedCandidateReceipt, CandidateReceipt, SigningContext, ValidatorSignature, AbridgedCandidateReceipt, CandidateReceipt, CompactStatement,
ValidatorIndex, ValidatorId, EncodeAs, Signed,
}
}; };
use parity_scale_codec::{Encode, Decode};
/// A statement, where the candidate receipt is included in the `Seconded` variant. /// A statement, where the candidate receipt is included in the `Seconded` variant.
#[derive(Debug, Clone, PartialEq, Encode, Decode)] #[derive(Debug, Clone, PartialEq, Encode, Decode)]
...@@ -42,54 +42,26 @@ pub enum Statement { ...@@ -42,54 +42,26 @@ pub enum Statement {
Invalid(Hash), Invalid(Hash),
} }
impl Statement { impl EncodeAs<CompactStatement> for Statement {
/// Get the signing payload of the statement. fn encode_as(&self) -> Vec<u8> {
pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> {
// convert to fully hash-based payload.
let statement = match *self { let statement = match *self {
Statement::Seconded(ref c) => polkadot_primitives::parachain::Statement::Candidate(c.hash()), Statement::Seconded(ref c) => {
Statement::Valid(hash) => polkadot_primitives::parachain::Statement::Valid(hash), polkadot_primitives::parachain::CompactStatement::Candidate(c.hash())
Statement::Invalid(hash) => polkadot_primitives::parachain::Statement::Invalid(hash), }
Statement::Valid(hash) => polkadot_primitives::parachain::CompactStatement::Valid(hash),
Statement::Invalid(hash) => polkadot_primitives::parachain::CompactStatement::Invalid(hash),
}; };
statement.encode()
statement.signing_payload(context)
} }
} }
/// A statement, the corresponding signature, and the index of the sender. /// A statement, the corresponding signature, and the index of the sender.
/// ///
/// Signing context and validator set should be apparent from context. /// Signing context and validator set should be apparent from context.
#[derive(Debug, Clone, PartialEq, Encode, Decode)] ///
pub struct SignedStatement { /// This statement is "full" in the sense that the `Seconded` variant includes the candidate receipt.
/// The statement signed. /// Only the compact `SignedStatement` is suitable for submission to the chain.
pub statement: Statement, pub type SignedFullStatement = Signed<Statement, CompactStatement>;
/// The signature of the validator.
pub signature: ValidatorSignature,
/// The index in the validator set of the signing validator. Which validator set should
/// be apparent from context.
pub sender: ValidatorIndex,
}
impl SignedStatement {
/// Check the signature on a statement. Provide a list of validators to index into
/// and the context in which the statement is presumably signed.
///
/// Returns an error if out of bounds or the signature is invalid. Otherwise, returns Ok.
pub fn check_signature(
&self,
validators: &[ValidatorId],
signing_context: &SigningContext,
) -> Result<(), ()> {
let validator = validators.get(self.sender as usize).ok_or(())?;
let payload = self.statement.signing_payload(signing_context);
if self.signature.verify(&payload[..], validator) {
Ok(())
} else {
Err(())
}
}
}
/// A misbehaviour report. /// A misbehaviour report.
pub enum MisbehaviorReport { pub enum MisbehaviorReport {
...@@ -101,9 +73,9 @@ pub enum MisbehaviorReport { ...@@ -101,9 +73,9 @@ pub enum MisbehaviorReport {
/// this message should be dispatched with all of them, in arbitrary order. /// 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'. /// 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 /// 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 /// This peer has seconded more than one parachain candidate for this relay parent head
DoubleVote(CandidateReceipt, SignedStatement, SignedStatement), DoubleVote(CandidateReceipt, SignedFullStatement, SignedFullStatement),
} }
...@@ -27,9 +27,9 @@ use super::{Hash, Balance, BlockNumber}; ...@@ -27,9 +27,9 @@ use super::{Hash, Balance, BlockNumber};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
#[cfg(feature = "std")] #[cfg(feature = "std")]
use primitives::bytes; use primitives::{bytes, crypto::Pair};
use primitives::RuntimeDebug; use primitives::RuntimeDebug;
use runtime_primitives::traits::Block as BlockT; use runtime_primitives::traits::{AppVerify, Block as BlockT};
use inherents::InherentIdentifier; use inherents::InherentIdentifier;
use application_crypto::KeyTypeId; use application_crypto::KeyTypeId;
...@@ -245,8 +245,6 @@ fn check_collator_signature<H: AsRef<[u8]>>( ...@@ -245,8 +245,6 @@ fn check_collator_signature<H: AsRef<[u8]>>(
collator: &CollatorId, collator: &CollatorId,
signature: &CollatorSignature, signature: &CollatorSignature,
) -> Result<(),()> { ) -> Result<(),()> {
use runtime_primitives::traits::AppVerify;
let payload = collator_signature_payload(relay_parent, parachain_index, pov_block_hash); let payload = collator_signature_payload(relay_parent, parachain_index, pov_block_hash);
if signature.verify(&payload[..], collator) { if signature.verify(&payload[..], collator) {
Ok(()) Ok(())
...@@ -594,7 +592,7 @@ pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8 ...@@ -594,7 +592,7 @@ pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8
/// actual values that are signed. /// actual values that are signed.
#[derive(Clone, PartialEq, Eq, Encode, Decode)] #[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))] #[cfg_attr(feature = "std", derive(Debug))]
pub enum Statement { pub enum CompactStatement {
/// Proposal of a parachain candidate. /// Proposal of a parachain candidate.
#[codec(index = "1")] #[codec(index = "1")]
Candidate(Hash), Candidate(Hash),
...@@ -606,14 +604,8 @@ pub enum Statement { ...@@ -606,14 +604,8 @@ pub enum Statement {
Invalid(Hash), Invalid(Hash),
} }
impl Statement { /// A signed compact statement, suitable to be sent to the chain.
/// Produce a payload on this statement that is used for signing. pub type SignedStatement = Signed<CompactStatement>;
///
/// It includes the context provided.
pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> {
(self, context).encode()
}
}
/// An either implicit or explicit attestation to the validity of a parachain /// An either implicit or explicit attestation to the validity of a parachain
/// candidate. /// candidate.
...@@ -647,11 +639,11 @@ impl ValidityAttestation { ...@@ -647,11 +639,11 @@ impl ValidityAttestation {
) -> Vec<u8> { ) -> Vec<u8> {
match *self { match *self {
ValidityAttestation::Implicit(_) => ( ValidityAttestation::Implicit(_) => (
Statement::Candidate(candidate_hash), CompactStatement::Candidate(candidate_hash),
signing_context, signing_context,
).encode(), ).encode(),
ValidityAttestation::Explicit(_) => ( ValidityAttestation::Explicit(_) => (
Statement::Valid(candidate_hash), CompactStatement::Valid(candidate_hash),
signing_context, signing_context,
).encode(), ).encode(),
} }
...@@ -723,64 +715,8 @@ impl From<BitVec<bitvec::order::Lsb0, u8>> for AvailabilityBitfield { ...@@ -723,64 +715,8 @@ impl From<BitVec<bitvec::order::Lsb0, u8>> for AvailabilityBitfield {
} }
} }
impl AvailabilityBitfield {
/// Encodes the signing payload into the given buffer.
pub fn encode_signing_payload_into<H: Encode>(
&self,
signing_context: &SigningContext<H>,
buf: &mut Vec<u8>,
) {
self.0.encode_to(buf);
signing_context.encode_to(buf);
}
/// Encodes the signing payload into a fresh byte-vector.
pub fn encode_signing_payload<H: Encode>(
&self,
signing_context: &SigningContext<H>,
) -> Vec<u8> {
let mut v = Vec::new();
self.encode_signing_payload_into(signing_context, &mut v);
v
}
}
/// A bitfield signed by a particular validator about the availability of pending candidates. /// A bitfield signed by a particular validator about the availability of pending candidates.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] pub type SignedAvailabilityBitfield = Signed<AvailabilityBitfield>;
pub struct SignedAvailabilityBitfield {
/// The index of the validator in the current set.
pub validator_index: ValidatorIndex,
/// The bitfield itself, with one bit per core. Only occupied cores may have the `1` bit set.
pub bitfield: AvailabilityBitfield,
/// The signature by the validator on the bitfield's signing payload. The context of the signature
/// should be apparent when checking the signature.
pub signature: ValidatorSignature,
}
/// Check a signature on an availability bitfield. Provide the bitfield, the validator who signed it,
/// the signature, the signing context, and an optional buffer in which to encode.
///
/// If the buffer is provided, it is assumed to be empty.
pub fn check_availability_bitfield_signature<H: Encode>(
bitfield: &AvailabilityBitfield,
validator: &ValidatorId,
signature: &ValidatorSignature,
signing_context: &SigningContext<H>,
payload_encode_buf: Option<&mut Vec<u8>>,
) -> Result<(),()> {
use runtime_primitives::traits::AppVerify;
let mut v = Vec::new();
let payload_encode_buf = payload_encode_buf.unwrap_or(&mut v);
bitfield.encode_signing_payload_into(signing_context, payload_encode_buf);
if signature.verify(&payload_encode_buf[..], validator) {
Ok(())
} else {
Err(())
}
}
/// A set of signed availability bitfields. Should be sorted by validator index, ascending. /// A set of signed availability bitfields. Should be sorted by validator index, ascending.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
...@@ -816,8 +752,6 @@ pub fn check_candidate_backing<H: AsRef<[u8]> + Encode>( ...@@ -816,8 +752,6 @@ pub fn check_candidate_backing<H: AsRef<[u8]> + Encode>(
group_len: usize, group_len: usize,
validator_lookup: impl Fn(usize) -> Option<ValidatorId>, validator_lookup: impl Fn(usize) -> Option<ValidatorId>,
) -> Result<usize, ()> { ) -> Result<usize, ()> {
use runtime_primitives::traits::AppVerify;
if backed.validator_indices.len() != group_len { if backed.validator_indices.len() != group_len {
return Err(()) return Err(())
} }
...@@ -885,6 +819,113 @@ pub mod id { ...@@ -885,6 +819,113 @@ pub mod id {
pub const PARACHAIN_HOST: ApiId = *b"parahost"; pub const PARACHAIN_HOST: ApiId = *b"parahost";
} }
/// This helper trait ensures that we can encode Statement as CompactStatement,
/// and anything as itself.
///
/// This resembles `parity_scale_codec::EncodeLike`, but it's distinct:
/// EncodeLike is a marker trait which asserts at the typesystem level that
/// one type's encoding is a valid encoding for another type. It doesn't
/// perform any type conversion when encoding.
///
/// This trait, on the other hand, provides a method which can be used to
/// simultaneously convert and encode one type as another.
pub trait EncodeAs<T> {
/// Convert Self into T, then encode T.
///
/// This is useful when T is a subset of Self, reducing encoding costs;
/// its signature also means that we do not need to clone Self in order
/// to retain ownership, as we would if we were to do
/// `self.clone().into().encode()`.
fn encode_as(&self) -> Vec<u8>;
}
impl<T: Encode> EncodeAs<T> for T {
fn encode_as(&self) -> Vec<u8> {
self.encode()
}
}
/// 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.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub 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,
/// This ensures the real payload is tracked at the typesystem level.
real_payload: sp_std::marker::PhantomData<RealPayload>,
}
// We can't bound this on `Payload: Into<RealPayload>` beacuse that conversion consumes
// the payload, and we don't want that. We can't bound it on `Payload: AsRef<RealPayload>`
// because there's no blanket impl of `AsRef<T> for T`. In the end, we just invent our
// own trait which does what we need: EncodeAs.
impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPayload> {
fn payload_data<H: Encode>(payload: &Payload, context: &SigningContext<H>) -> Vec<u8> {
// equivalent to (real_payload, context).encode()
let mut out = payload.encode_as();
out.extend(context.encode());
out
}
/// Sign this payload with the given context and key, storing the validator index.
#[cfg(feature = "std")]
pub fn sign<H: Encode>(
payload: Payload,
context: &SigningContext<H>,
validator_index: ValidatorIndex,
key: &ValidatorPair,
) -> Self {
let data = Self::payload_data(&payload, context);
let signature = key.sign(&data);
Self {
payload,
validator_index,
signature,
real_payload: std::marker::PhantomData,
}
}
/// Validate the payload given the context and public key.
pub fn check_signature<H: Encode>(&self, context: &SigningContext<H>, key: &ValidatorId) -> Result<(), ()> {
let data = Self::payload_data(&self.payload, context);
if self.signature.verify(data.as_slice(), key) { Ok(()) } else { Err(()) }
}
/// Immutably access the payload.
#[inline]
pub fn payload(&self) -> &Payload {
&self.payload
}
/// Immutably access the validator index.
#[inline]
pub fn validator_index(&self) -> ValidatorIndex {
self.validator_index
}
/// Immutably access the signature.
#[inline]
pub fn signature(&self) -> &ValidatorSignature {
&self.signature
}
/// Discard signing data, get the payload
// Note: can't `impl<P, R> From<Signed<P, R>> for P` because the orphan rule exception doesn't
// handle this case yet. Likewise can't `impl<P, R> Into<P> for Signed<P, R>` because it might
// potentially conflict with the global blanket impl, even though it currently doesn't.
#[inline]
pub fn into_payload(self) -> Payload {
self.payload
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
......
...@@ -35,11 +35,11 @@ The Statement Distribution subsystem sends statements to peer nodes and detects ...@@ -35,11 +35,11 @@ The Statement Distribution subsystem sends statements to peer nodes and detects
## Peer Receipt State Machine ## 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. 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 ...@@ -5,21 +5,15 @@ candidates for the duration of a challenge period. This is done via an erasure-c
## Signed Availability Bitfield ## 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 ```rust
struct SignedAvailabilityBitfield { pub type SignedAvailabilityBitfield = Signed<Bitvec>;
validator_index: ValidatorIndex,
bitfield: Bitvec,
signature: ValidatorSignature,
}
struct Bitfields(Vec<(SignedAvailabilityBitfield)>), // bitfields sorted by validator index, ascending 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 ## 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. 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 { ...@@ -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 ## Statement Type
The [Candidate Backing subsystem](../node/backing/candidate-backing.md) issues and signs these after candidate validation. The [Candidate Backing subsystem](../node/backing/candidate-backing.md) issues and signs these after candidate validation.
...@@ -38,28 +67,38 @@ enum Statement { ...@@ -38,28 +67,38 @@ enum Statement {
/// A statement about the invalidity of a candidate. /// A statement about the invalidity of a candidate.
Invalid(Hash), Invalid(Hash),
} }
/// 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),
}
``` ```
`CompactStatement` exists because a `CandidateReceipt` includes `HeadData`, which does not have a bounded size.
## Signed Statement Type ## Signed Statement Type
A statement, the identifier of a validator, and a signature. A statement which has been [cryptographically signed](#signed-wrapper) by a validator.
```rust ```rust
/// A signed statement. /// A signed statement, containing the abridged candidate receipt in the `Seconded` variant.
struct SignedStatement { pub type SignedFullStatement = Signed<Statement, CompactStatement>;
/// 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
}
```
The actual signed payload will be the SCALE encoding of `(compact_statement, signing_context)` where /// A signed statement, containing only the hash.
`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). pub type SignedStatement = Signed<CompactStatement>;
```
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. 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 ## Backed Candidate
......
...@@ -158,11 +158,11 @@ enum MisbehaviorReport { ...@@ -158,11 +158,11 @@ enum MisbehaviorReport {
/// this message should be dispatched with all of them, in arbitrary order. /// this message should be dispatched with all of them, in arbitrary order.
///