Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
move EncodeAs, Signed from node::primitives to primitives::parachain
Browse files Browse the repository at this point in the history
  • Loading branch information
coriolinus committed Jun 18, 2020
1 parent f974e48 commit dde56cc
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 171 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion node/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ polkadot-primitives = { path = "../../primitives" }
polkadot-statement-table = { path = "../../statement-table" }
parity-scale-codec = { version = "1.3.0", default-features = false, features = ["derive"] }
runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
154 changes: 5 additions & 149 deletions node/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,7 @@
//! there.

use parity_scale_codec::{Decode, Encode};
use polkadot_primitives::parachain::{
AbridgedCandidateReceipt, CandidateReceipt, SigningContext, ValidatorId, ValidatorIndex,
ValidatorPair, ValidatorSignature,
};
use polkadot_primitives::Hash;
use runtime_primitives::traits::AppVerify;
use sp_core::crypto::Pair;
use polkadot_primitives::{parachain::{AbridgedCandidateReceipt, CandidateReceipt, CompactStatement, EncodeAs, Signed}, Hash};

/// A statement, where the candidate receipt is included in the `Seconded` variant.
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
Expand All @@ -43,161 +37,23 @@ pub enum Statement {
Invalid(Hash),
}

impl Statement {
/// Get the signing payload of the statement.
pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> {
// convert to fully hash-based payload.
let statement = match *self {
Statement::Seconded(ref c) => polkadot_primitives::parachain::Statement::Candidate(c.hash()),
Statement::Valid(hash) => polkadot_primitives::parachain::Statement::Valid(hash),
Statement::Invalid(hash) => polkadot_primitives::parachain::Statement::Invalid(hash),
};

statement.signing_payload(context)
}
}

/// This helper trait ensures that we can encode Statement as CompactStatement,
/// and anything as itself.
pub trait EncodeAs<T> {
fn encode_as(&self) -> Vec<u8>;
}

impl<T: Encode> EncodeAs<T> for T {
fn encode_as(&self) -> Vec<u8> {
self.encode()
}
}

/// 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 a `CandidateReceipt` contains `HeadData`, which does not have
/// bounded size. By using this enum instead, we ensure that the production and validation of signatures is fast
/// while retaining their necessary cryptographic properties.
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
enum CompactStatement {
/// A statement about a new candidate being seconded by a validator. This is an implicit validity vote.
#[codec(index = "1")]
Seconded(Hash),
/// A statement about the validity of a candidate, based on candidate's hash.
#[codec(index = "2")]
Valid(Hash),
/// A statement about the invalidity of a candidate.
#[codec(index = "3")]
Invalid(Hash),
}

impl EncodeAs<CompactStatement> for Statement {
fn encode_as(&self) -> Vec<u8> {
let statement = match *self {
Statement::Seconded(ref c) => {
polkadot_primitives::parachain::Statement::Candidate(c.hash())
polkadot_primitives::parachain::CompactStatement::Candidate(c.hash())
}
Statement::Valid(hash) => polkadot_primitives::parachain::Statement::Valid(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()
}
}

/// 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.
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: 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(payload: &Payload, context: SigningContext) -> Vec<u8> {
(payload.encode_as(), context).encode()
}

pub fn sign(
payload: Payload,
context: SigningContext,
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,
}
}

pub fn validate(&self, context: SigningContext, key: &ValidatorId) -> bool {
let data = Self::payload_data(&self.payload, context);
self.signature.verify(data.as_slice(), key)
}

#[inline]
pub fn payload(&self) -> &Payload {
&self.payload
}

#[inline]
pub fn validator_index(&self) -> ValidatorIndex {
self.validator_index
}

#[inline]
pub fn signature(&self) -> &ValidatorSignature {
&self.signature
}
}

/// A statement, the corresponding signature, and the index of the sender.
///
/// Signing context and validator set should be apparent from context.
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub struct SignedStatement {
/// The statement signed.
pub statement: Statement,
/// 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(())
}
}
}
pub type SignedStatement = Signed<Statement, CompactStatement>;

/// A misbehaviour report.
pub enum MisbehaviorReport {
Expand Down
114 changes: 98 additions & 16 deletions primitives/src/parachain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ use super::{Hash, Balance, BlockNumber};
use serde::{Serialize, Deserialize};

#[cfg(feature = "std")]
use primitives::bytes;
use primitives::{bytes, crypto::Pair};
use primitives::RuntimeDebug;
use runtime_primitives::traits::Block as BlockT;
use runtime_primitives::traits::{AppVerify, Block as BlockT};
use inherents::InherentIdentifier;
use application_crypto::KeyTypeId;

Expand Down Expand Up @@ -245,8 +245,6 @@ fn check_collator_signature(
collator: &CollatorId,
signature: &CollatorSignature,
) -> Result<(),()> {
use runtime_primitives::traits::AppVerify;

let payload = collator_signature_payload(relay_parent, parachain_index, pov_block_hash);
if signature.verify(&payload[..], collator) {
Ok(())
Expand Down Expand Up @@ -581,7 +579,7 @@ pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8
/// actual values that are signed.
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum Statement {
pub enum CompactStatement {
/// Proposal of a parachain candidate.
#[codec(index = "1")]
Candidate(Hash),
Expand All @@ -593,15 +591,6 @@ pub enum Statement {
Invalid(Hash),
}

impl Statement {
/// Produce a payload on this statement that is used for signing.
///
/// 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
/// candidate.
#[derive(Clone, Eq, PartialEq, Decode, Encode, RuntimeDebug)]
Expand Down Expand Up @@ -727,8 +716,6 @@ pub fn check_availability_bitfield_signature<H: Encode>(
signing_context: &SigningContext,
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);

Expand Down Expand Up @@ -792,6 +779,101 @@ pub mod id {
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, 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(payload: &Payload, context: SigningContext) -> Vec<u8> {
(payload.encode_as(), context).encode()
}

/// Sign this payload with the given context and key, storing the validator index.
#[cfg(feature = "std")]
pub fn sign(
payload: Payload,
context: SigningContext,
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 validate(&self, context: SigningContext, key: &ValidatorId) -> bool {
let data = Self::payload_data(&self.payload, context);
self.signature.verify(data.as_slice(), key)
}

/// 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
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion runtime/common/src/parachains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use primitives::{
Balance,
BlockNumber,
parachain::{
Id as ParaId, Chain, DutyRoster, AttestedCandidate, Statement, ParachainDispatchOrigin,
Id as ParaId, Chain, DutyRoster, AttestedCandidate, CompactStatement as Statement, ParachainDispatchOrigin,
UpwardMessage, ValidatorId, ActiveParas, CollatorId, Retriable, OmittedValidationData,
CandidateReceipt, GlobalValidationSchedule, AbridgedCandidateReceipt,
LocalValidationData, Scheduling, ValidityAttestation, NEW_HEADS_IDENTIFIER, PARACHAIN_KEY_TYPE_ID,
Expand Down
2 changes: 1 addition & 1 deletion runtime/common/src/registrar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ mod tests {
use primitives::{
parachain::{
ValidatorId, Info as ParaInfo, Scheduling, LOWEST_USER_ID, AttestedCandidate,
CandidateReceipt, HeadData, ValidityAttestation, Statement, Chain,
CandidateReceipt, HeadData, ValidityAttestation, CompactStatement as Statement, Chain,
CollatorPair, CandidateCommitments,
},
Balance, BlockNumber, Header, Signature,
Expand Down
2 changes: 1 addition & 1 deletion statement-table/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub mod generic;
pub use generic::Table;

use primitives::parachain::{
Id, AbridgedCandidateReceipt, Statement as PrimitiveStatement, ValidatorSignature, ValidatorIndex,
Id, AbridgedCandidateReceipt, CompactStatement as PrimitiveStatement, ValidatorSignature, ValidatorIndex,
};
use primitives::Hash;

Expand Down
2 changes: 1 addition & 1 deletion validation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use std::{
use codec::Encode;
use polkadot_primitives::parachain::{
Id as ParaId, Chain, DutyRoster, AbridgedCandidateReceipt,
Statement as PrimitiveStatement,
CompactStatement as PrimitiveStatement,
PoVBlock, ErasureChunk, ValidatorSignature, ValidatorIndex,
ValidatorPair, ValidatorId, SigningContext,
};
Expand Down

0 comments on commit dde56cc

Please sign in to comment.