diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock
index e48886a2b08ece37783d31b3f9e264413e2fe8ad..031f60f6eb6ef5168dad9e91c368c0edeae5eb93 100644
--- a/polkadot/Cargo.lock
+++ b/polkadot/Cargo.lock
@@ -203,6 +203,11 @@ name = "bitflags"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "bitvec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "blake2"
 version = "0.7.1"
@@ -2023,6 +2028,7 @@ dependencies = [
 name = "polkadot-runtime"
 version = "0.1.0"
 dependencies = [
+ "bitvec 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "hex-literal 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -4099,6 +4105,7 @@ dependencies = [
 "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
 "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
 "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
+"checksum bitvec 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e37e2176261200377c7cde4c6de020394174df556c356f965e4bc239f5ce1c5a"
 "checksum blake2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73b77e29dbd0115e43938be2d5128ecf81c0353e00acaa65339a1242586951d9"
 "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
 "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab"
diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs
index 4601b5b30d428cbcbbd76f2826e908ecaff98c5f..ccf330856b18b2cbe0277f9371c0b92a7d73a115 100644
--- a/polkadot/consensus/src/lib.rs
+++ b/polkadot/consensus/src/lib.rs
@@ -74,7 +74,7 @@ use parking_lot::Mutex;
 use polkadot_primitives::{Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey};
 use polkadot_primitives::{Compact, UncheckedExtrinsic};
 use polkadot_primitives::parachain::{Id as ParaId, Chain, DutyRoster, BlockData, Extrinsic as ParachainExtrinsic, CandidateReceipt, CandidateSignature};
-use polkadot_primitives::parachain::ParachainHost;
+use polkadot_primitives::parachain::{AttestedCandidate, ParachainHost, Statement as PrimitiveStatement};
 use primitives::{AuthorityId, ed25519};
 use runtime_primitives::traits::ProvideRuntimeApi;
 use tokio::runtime::TaskExecutor;
@@ -156,7 +156,10 @@ pub struct GroupInfo {
 /// The actual message signed is the encoded statement concatenated with the
 /// parent hash.
 pub fn sign_table_statement(statement: &Statement, key: &ed25519::Pair, parent_hash: &Hash) -> CandidateSignature {
-	let mut encoded = statement.encode();
+	// we sign using the primitive statement type because that's what the runtime
+	// expects. These types probably encode the same way so this clone could be optimized
+	// out in the future.
+	let mut encoded = PrimitiveStatement::from(statement.clone()).encode();
 	encoded.extend(parent_hash.as_ref());
 
 	key.sign(&encoded).into()
@@ -166,7 +169,7 @@ pub fn sign_table_statement(statement: &Statement, key: &ed25519::Pair, parent_h
 pub fn check_statement(statement: &Statement, signature: &CandidateSignature, signer: SessionKey, parent_hash: &Hash) -> bool {
 	use runtime_primitives::traits::Verify;
 
-	let mut encoded = statement.encode();
+	let mut encoded = PrimitiveStatement::from(statement.clone()).encode();
 	encoded.extend(parent_hash.as_ref());
 
 	signature.verify(&encoded[..], &signer.into())
@@ -640,7 +643,7 @@ impl<C, TxApi> CreateProposal<C, TxApi> where
 	C: ProvideRuntimeApi + HeaderBackend<Block> + Send + Sync,
 	C::Api: ParachainHost<Block> + BlockBuilder<Block>,
 {
-	fn propose_with(&self, candidates: Vec<CandidateReceipt>) -> Result<Block, Error> {
+	fn propose_with(&self, candidates: Vec<AttestedCandidate>) -> Result<Block, Error> {
 		use client::block_builder::BlockBuilder;
 		use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
 		use polkadot_primitives::InherentData;
@@ -726,9 +729,7 @@ impl<C, TxApi> Future for CreateProposal<C, TxApi> where
 		try_ready!(self.timing.poll(included));
 
 		// 2. propose
-		let proposed_candidates = self.table.with_proposal(|proposed_set| {
-			proposed_set.into_iter().cloned().collect()
-		});
+		let proposed_candidates = self.table.proposed_set();
 
 		self.propose_with(proposed_candidates).map(Async::Ready)
 	}
@@ -738,6 +739,7 @@ impl<C, TxApi> Future for CreateProposal<C, TxApi> where
 mod tests {
 	use super::*;
 	use substrate_keyring::Keyring;
+	use polkadot_primitives::parachain::Statement as PStatement;
 
 	#[test]
 	fn sign_and_check_statement() {
diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs
index a4ad40a80afd121dee48eb444a49891283983005..a9a848e14b5efd51a789d7649e768a77de7f7b5b 100644
--- a/polkadot/consensus/src/service.rs
+++ b/polkadot/consensus/src/service.rs
@@ -94,7 +94,7 @@ fn prune_unneeded_availability<C>(client: Arc<C>, extrinsic_store: ExtrinsicStor
 				.iter()
 				.filter_map(|ex| match ex.function {
 					Call::Parachains(ParachainsCall::set_heads(ref heads)) =>
-						Some(heads.iter().map(|c| c.hash()).collect()),
+						Some(heads.iter().map(|c| c.candidate.hash()).collect()),
 					_ => None,
 				})
 				.next()
diff --git a/polkadot/consensus/src/shared_table/mod.rs b/polkadot/consensus/src/shared_table/mod.rs
index 7c0914ecf95bc352d7b34dfcbac73d56a5d8b6ba..34087566214a67738fcdbbdf2f266f26542c2bd5 100644
--- a/polkadot/consensus/src/shared_table/mod.rs
+++ b/polkadot/consensus/src/shared_table/mod.rs
@@ -23,7 +23,10 @@ use std::sync::Arc;
 use extrinsic_store::{Data, Store as ExtrinsicStore};
 use table::{self, Table, Context as TableContextTrait};
 use polkadot_primitives::{Hash, SessionKey};
-use polkadot_primitives::parachain::{Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt};
+use polkadot_primitives::parachain::{
+	Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt,
+	AttestedCandidate,
+};
 
 use parking_lot::Mutex;
 use futures::{future, prelude::*};
@@ -439,14 +442,25 @@ impl SharedTable {
 		f(inner.table.get_candidate(digest))
 	}
 
-	/// Execute a closure using the current proposed set.
-	///
-	/// Deadlocks if called recursively.
-	pub fn with_proposal<F, U>(&self, f: F) -> U
-		where F: FnOnce(Vec<&CandidateReceipt>) -> U
-	{
-		let inner = self.inner.lock();
-		f(inner.table.proposed_candidates(&*self.context))
+	/// Get a set of candidates that can be proposed.
+	pub fn proposed_set(&self) -> Vec<AttestedCandidate> {
+		use table::generic::{ValidityAttestation as GAttestation};
+		use polkadot_primitives::parachain::ValidityAttestation;
+
+		// we transform the types of the attestations gathered from the table
+		// into the type expected by the runtime. This may do signature
+		// aggregation in the future.
+		let table_attestations = self.inner.lock().table.proposed_candidates(&*self.context);
+		table_attestations.into_iter()
+			.map(|attested| AttestedCandidate {
+				candidate: attested.candidate,
+				availability_votes: attested.availability_votes,
+				validity_votes: attested.validity_votes.into_iter().map(|(a, v)| match v {
+					GAttestation::Implicit(s) => (a, ValidityAttestation::Implicit(s)),
+					GAttestation::Explicit(s) => (a, ValidityAttestation::Explicit(s)),
+				}).collect(),
+			})
+			.collect()
 	}
 
 	/// Get the number of total parachains.
diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs
index 01acdc4564b5b0090ddd1d31c43996d74364df6d..321060db3fb6a325c13dbe91ed480cd398601d7f 100644
--- a/polkadot/primitives/src/lib.rs
+++ b/polkadot/primitives/src/lib.rs
@@ -113,6 +113,6 @@ impl Extrinsic for UncheckedExtrinsic {}
 pub struct InherentData {
 	/// Current timestamp.
 	pub timestamp: Timestamp,
-	/// Parachain heads update.
-	pub parachain_heads: Vec<::parachain::CandidateReceipt>,
+	/// Parachain heads update. This contains fully-attested candidates.
+	pub parachain_heads: Vec<::parachain::AttestedCandidate>,
 }
diff --git a/polkadot/primitives/src/parachain.rs b/polkadot/primitives/src/parachain.rs
index 29fecaae735979bea15ee95f3b1574bc26743181..2e209011ab73504b89a9e287e67fcb25789653c0 100644
--- a/polkadot/primitives/src/parachain.rs
+++ b/polkadot/primitives/src/parachain.rs
@@ -18,7 +18,7 @@
 
 use rstd::prelude::*;
 use rstd::cmp::Ordering;
-use super::Hash;
+use super::{Hash, SessionKey};
 
 use {AccountId};
 
@@ -102,7 +102,6 @@ pub struct CandidateReceipt {
 
 impl CandidateReceipt {
 	/// Get the blake2_256 hash
-	#[cfg(feature = "std")]
 	pub fn hash(&self) -> Hash {
 		use runtime_primitives::traits::{BlakeTwo256, Hash};
 		BlakeTwo256::hash_of(self)
@@ -189,25 +188,68 @@ pub struct HeadData(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 pub struct ValidationCode(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
 
-/// Activitiy bit field
+/// Activity bit field
 #[derive(PartialEq, Eq, Clone, Default, Encode, Decode)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
 pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8>);
 
 /// Statements which can be made about parachain candidates.
-#[derive(Clone, PartialEq, Eq)]
-#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
+#[derive(Clone, PartialEq, Eq, Encode)]
+#[cfg_attr(feature = "std", derive(Debug))]
 pub enum Statement {
 	/// Proposal of a parachain candidate.
+	#[codec(index = "1")]
 	Candidate(CandidateReceipt),
 	/// State that a parachain candidate is valid.
+	#[codec(index = "2")]
 	Valid(Hash),
-	/// Vote to commit to a candidate.
+	/// State a candidate is invalid.
+	#[codec(index = "3")]
 	Invalid(Hash),
-	/// Vote to advance round after inactive primary.
+	/// State a candidate's associated data is unavailable.
+	#[codec(index = "4")]
 	Available(Hash),
 }
 
+/// An either implicit or explicit attestation to the validity of a parachain
+/// candidate.
+#[derive(Clone, PartialEq, Decode, Encode)]
+#[cfg_attr(feature = "std", derive(Debug))]
+pub enum ValidityAttestation {
+	/// implicit validity attestation by issuing.
+	/// This corresponds to issuance of a `Candidate` statement.
+	#[codec(index = "1")]
+	Implicit(CandidateSignature),
+	/// An explicit attestation. This corresponds to issuance of a
+	/// `Valid` statement.
+	#[codec(index = "2")]
+	Explicit(CandidateSignature),
+}
+
+/// An attested candidate.
+#[derive(Clone, PartialEq, Decode, Encode)]
+#[cfg_attr(feature = "std", derive(Debug))]
+pub struct AttestedCandidate {
+	/// The candidate data.
+	pub candidate: CandidateReceipt,
+	/// Validity attestations.
+	pub validity_votes: Vec<(SessionKey, ValidityAttestation)>,
+	/// Availability attestations.
+	pub availability_votes: Vec<(SessionKey, CandidateSignature)>,
+}
+
+impl AttestedCandidate {
+	/// Get the candidate.
+	pub fn candidate(&self) -> &CandidateReceipt {
+		&self.candidate
+	}
+
+	/// Get the group ID of the candidate.
+	pub fn parachain_index(&self) -> Id {
+		self.candidate.parachain_index
+	}
+}
+
 decl_runtime_apis! {
 	/// The API for querying the state of parachains on-chain.
 	pub trait ParachainHost {
diff --git a/polkadot/runtime/Cargo.toml b/polkadot/runtime/Cargo.toml
index 241e7c4450df1c408f34d576595f25010445a1a6..2398d27e73a74e2c3907d9bc6b7f793ebbde7a2f 100644
--- a/polkadot/runtime/Cargo.toml
+++ b/polkadot/runtime/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
 authors = ["Parity Technologies <admin@parity.io>"]
 
 [dependencies]
+bitvec = { version = "0.8", default-features = false, features = ["alloc"] }
 rustc-hex = "1.0"
 log = { version = "0.3", optional = true }
 serde = { version = "1.0", default-features = false }
@@ -17,7 +18,6 @@ sr-std = { git = "https://github.com/paritytech/substrate" }
 sr-io = { git = "https://github.com/paritytech/substrate" }
 srml-support = { git = "https://github.com/paritytech/substrate" }
 substrate-primitives = { git = "https://github.com/paritytech/substrate" }
-substrate-keyring = { git = "https://github.com/paritytech/substrate" }
 substrate-client = { git = "https://github.com/paritytech/substrate" }
 srml-balances = { git = "https://github.com/paritytech/substrate" }
 srml-consensus = { git = "https://github.com/paritytech/substrate" }
@@ -34,10 +34,12 @@ sr-version = { git = "https://github.com/paritytech/substrate" }
 
 [dev-dependencies]
 hex-literal = "0.1.0"
+substrate-keyring = { git = "https://github.com/paritytech/substrate" }
 
 [features]
 default = ["std"]
 std = [
+	"bitvec/std",
 	"polkadot-primitives/std",
 	"parity-codec/std",
 	"parity-codec-derive/std",
diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs
index 133ce258dabfff3394f219e6acc6bb94d583cf21..4db8767dceb25fb26f12a21f5e84390daa644bf0 100644
--- a/polkadot/runtime/src/lib.rs
+++ b/polkadot/runtime/src/lib.rs
@@ -20,6 +20,9 @@
 // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
 #![recursion_limit="256"]
 
+#[macro_use]
+extern crate bitvec;
+
 #[macro_use]
 extern crate parity_codec_derive;
 extern crate parity_codec as codec;
@@ -52,6 +55,9 @@ extern crate srml_treasury as treasury;
 
 extern crate polkadot_primitives as primitives;
 
+#[cfg(test)]
+extern crate substrate_keyring as keyring;
+
 mod parachains;
 
 #[cfg(feature = "std")]
diff --git a/polkadot/runtime/src/parachains.rs b/polkadot/runtime/src/parachains.rs
index 0426ea7d5b5f12a11013ed90f7f517b6f418e96c..0f5e781e49939a9a7138df5c555c0ddc4cf59678 100644
--- a/polkadot/runtime/src/parachains.rs
+++ b/polkadot/runtime/src/parachains.rs
@@ -19,9 +19,11 @@
 use rstd::prelude::*;
 use codec::Decode;
 
-use sr_primitives::{RuntimeString, traits::{Extrinsic, Block as BlockT,
-	Hash, BlakeTwo256, ProvideInherent}};
-use primitives::parachain::{Id, Chain, DutyRoster, CandidateReceipt};
+use bitvec::BigEndian;
+use sr_primitives::{RuntimeString, traits::{
+	Extrinsic, Block as BlockT, Hash as HashT, BlakeTwo256, ProvideInherent,
+}};
+use primitives::parachain::{Id, Chain, DutyRoster, AttestedCandidate, Statement};
 use {system, session};
 
 use srml_support::{StorageValue, StorageMap};
@@ -77,7 +79,7 @@ decl_module! {
 	/// Parachains module.
 	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
 		/// Provide candidate receipts for parachains, in ascending order by id.
-		fn set_heads(origin, heads: Vec<CandidateReceipt>) -> Result {
+		fn set_heads(origin, heads: Vec<AttestedCandidate>) -> Result {
 			ensure_inherent(origin)?;
 			ensure!(!<DidUpdate<T>>::exists(), "Parachain heads must be updated only once in the block");
 			ensure!(
@@ -98,23 +100,26 @@ decl_module! {
 				for head in &heads {
 					// proposed heads must be ascending order by parachain ID without duplicate.
 					ensure!(
-						last_id.as_ref().map_or(true, |x| x < &head.parachain_index),
+						last_id.as_ref().map_or(true, |x| x < &head.parachain_index()),
 						"Parachain candidates out of order by ID"
 					);
 
 					// must be unknown since active parachains are always sorted.
 					ensure!(
-						iter.find(|x| x == &&head.parachain_index).is_some(),
+						iter.find(|x| x == &&head.parachain_index()).is_some(),
 						"Submitted candidate for unregistered or out-of-order parachain {}"
 					);
 
-					last_id = Some(head.parachain_index);
+					last_id = Some(head.parachain_index());
 				}
 			}
 
+			Self::check_attestations(&heads)?;
+
+
 			for head in heads {
-				let id = head.parachain_index.clone();
-				<Heads<T>>::insert(id, head.head_data.0);
+				let id = head.parachain_index();
+				<Heads<T>>::insert(id, head.candidate.head_data.0);
 			}
 
 			<DidUpdate<T>>::put(true);
@@ -158,6 +163,18 @@ decl_module! {
 	}
 }
 
+fn majority_of(list_len: usize) -> usize {
+	list_len / 2 + list_len % 2
+}
+
+fn localized_payload(statement: Statement, parent_hash: ::primitives::Hash) -> Vec<u8> {
+	use codec::Encode;
+
+	let mut encoded = statement.encode();
+	encoded.extend(parent_hash.as_ref());
+	encoded
+}
+
 impl<T: Trait> Module<T> {
 	/// Calculate the current block's duty roster using system's random seed.
 	pub fn calculate_duty_roster() -> DutyRoster {
@@ -208,6 +225,179 @@ impl<T: Trait> Module<T> {
 		}
 	}
 
+	// check the attestations on these candidates. The candidates should have been checked
+	// that each candidates' chain ID is valid.
+	fn check_attestations(attested_candidates: &[AttestedCandidate]) -> Result {
+		use primitives::parachain::ValidityAttestation;
+		use sr_primitives::traits::Verify;
+
+		// returns groups of slices that have the same chain ID.
+		// assumes the inner slice is sorted by id.
+		struct GroupedDutyIter<'a> {
+			next_idx: usize,
+			inner: &'a [(usize, Id)],
+		}
+
+		impl<'a> GroupedDutyIter<'a> {
+			fn new(inner: &'a [(usize, Id)]) -> Self {
+				GroupedDutyIter { next_idx: 0, inner }
+			}
+
+			fn group_for(&mut self, wanted_id: Id) -> Option<&'a [(usize, Id)]> {
+				while let Some((id, keys)) = self.next() {
+					if wanted_id == id {
+						return Some(keys)
+					}
+				}
+
+				None
+			}
+		}
+
+		impl<'a> Iterator for GroupedDutyIter<'a> {
+			type Item = (Id, &'a [(usize, Id)]);
+
+			fn next(&mut self) -> Option<Self::Item> {
+				if self.next_idx == self.inner.len() { return None }
+				let start_idx = self.next_idx;
+				self.next_idx += 1;
+				let start_id = self.inner[start_idx].1;
+
+				while self.inner.get(self.next_idx).map_or(false, |&(_, ref id)| id == &start_id) {
+					self.next_idx += 1;
+				}
+
+				Some((start_id, &self.inner[start_idx..self.next_idx]))
+			}
+		}
+
+		let authorities = super::Consensus::authorities();
+		let duty_roster = Self::calculate_duty_roster();
+
+		// convert a duty roster, which is originally a Vec<Chain>, where each
+		// item corresponds to the same position in the session keys, into
+		// a list containing (index, parachain duty) where indices are into the session keys.
+		// this list is sorted ascending by parachain duty, just like the
+		// parachain candidates are.
+		let make_sorted_duties = |duty: &[Chain]| {
+			let mut sorted_duties = Vec::with_capacity(duty.len());
+			for (val_idx, duty) in duty.iter().enumerate() {
+				let id = match duty {
+					Chain::Relay => continue,
+					Chain::Parachain(id) => id,
+				};
+
+				let idx = sorted_duties.binary_search_by_key(&id, |&(_, ref id)| id)
+					.unwrap_or_else(|idx| idx);
+
+				sorted_duties.insert(idx, (val_idx, *id));
+			}
+
+			sorted_duties
+		};
+
+		let sorted_validators = make_sorted_duties(&duty_roster.validator_duty);
+		let sorted_guarantors = make_sorted_duties(&duty_roster.guarantor_duty);
+
+		let parent_hash = super::System::parent_hash();
+		let localized_payload = |statement: Statement| localized_payload(statement, parent_hash);
+
+		let mut validator_groups = GroupedDutyIter::new(&sorted_validators[..]);
+		let mut guarantor_groups = GroupedDutyIter::new(&sorted_guarantors[..]);
+
+		for candidate in attested_candidates {
+			let validator_group = validator_groups.group_for(candidate.parachain_index())
+				.ok_or("no validator group for parachain")?;
+
+			let availability_group = guarantor_groups.group_for(candidate.parachain_index())
+				.ok_or("no availability group for parachain")?;
+
+			ensure!(
+				candidate.validity_votes.len() >= majority_of(validator_group.len()),
+				"Not enough validity attestations"
+			);
+
+			ensure!(
+				candidate.availability_votes.len() >= majority_of(availability_group.len()),
+				"Not enough availability attestations"
+			);
+
+			let mut candidate_hash = None;
+			let mut encoded_implicit = None;
+			let mut encoded_explicit = None;
+
+			// track which voters have voted already. the first `authorities.len()`
+			// bits is for validity, the next are for availability.
+			let mut track_voters = bitvec![0; authorities.len() * 2];
+			for (auth_id, validity_attestation) in &candidate.validity_votes {
+				// protect against double-votes.
+				match validator_group.iter().find(|&(idx, _)| &authorities[*idx] == auth_id) {
+					None => return Err("Attesting validator not on this chain's validation duty."),
+					Some(&(idx, _)) => {
+						if track_voters.get(idx) {
+							return Err("Voter already attested validity once")
+						}
+						track_voters.set(idx, true)
+					}
+				}
+
+				let (payload, sig) = match validity_attestation {
+					ValidityAttestation::Implicit(sig) => {
+						let payload = encoded_implicit.get_or_insert_with(|| localized_payload(
+							Statement::Candidate(candidate.candidate.clone()),
+						));
+
+						(payload, sig)
+					}
+					ValidityAttestation::Explicit(sig) => {
+						let hash = candidate_hash
+							.get_or_insert_with(|| candidate.candidate.hash())
+							.clone();
+
+						let payload = encoded_explicit.get_or_insert_with(|| localized_payload(
+							Statement::Valid(hash),
+						));
+
+						(payload, sig)
+					}
+				};
+
+				ensure!(
+					sig.verify(&payload[..], &auth_id.0.into()),
+					"Candidate validity attestation signature is bad."
+				);
+			}
+
+			let mut encoded_available = None;
+			for (auth_id, sig) in &candidate.availability_votes {
+				match availability_group.iter().find(|&(idx, _)| &authorities[*idx] == auth_id) {
+					None => return Err("Attesting validator not on this chain's availability duty."),
+					Some(&(idx, _)) => {
+						if track_voters.get(authorities.len() + idx) {
+							return Err("Voter already attested availability once")
+						}
+						track_voters.set(authorities.len() + idx, true)
+					}
+				}
+
+				let hash = candidate_hash
+					.get_or_insert_with(|| candidate.candidate.hash())
+					.clone();
+
+				let payload = encoded_available.get_or_insert_with(|| localized_payload(
+					Statement::Available(hash),
+				));
+
+				ensure!(
+					sig.verify(&payload[..], &auth_id.0.into()),
+					"Candidate availability attestation signature is bad."
+				)
+			}
+		}
+
+		Ok(())
+	}
+
 /*
 	// TODO: Consider integrating if needed.
 	/// Extract the parachain heads from the block.
@@ -226,7 +416,7 @@ impl<T: Trait> Module<T> {
 }
 
 impl<T: Trait> ProvideInherent for Module<T> {
-	type Inherent = Vec<CandidateReceipt>;
+	type Inherent = Vec<AttestedCandidate>;
 	type Call = Call<T>;
 	type Error = RuntimeString;
 
@@ -259,9 +449,10 @@ mod tests {
 	use rstd::marker::PhantomData;
 	use sr_io::{TestExternalities, with_externalities};
 	use substrate_primitives::{H256, Blake2Hasher};
-	use sr_primitives::BuildStorage;
-	use sr_primitives::traits::{Identity, BlakeTwo256};
-	use sr_primitives::testing::{Digest, Header, DigestItem};
+	use sr_primitives::{generic, BuildStorage};
+	use sr_primitives::traits::BlakeTwo256;
+	use primitives::{parachain::{CandidateReceipt, HeadData, ValidityAttestation}, SessionKey};
+	use keyring::Keyring;
 	use {consensus, timestamp};
 
 	impl_outer_origin! {
@@ -272,24 +463,24 @@ mod tests {
 	pub struct Test;
 	impl consensus::Trait for Test {
 		const NOTE_OFFLINE_POSITION: u32 = 1;
-		type SessionKey = u64;
+		type SessionKey = SessionKey;
 		type OnOfflineValidator = ();
-		type Log = DigestItem;
+		type Log = ::Log;
 	}
 	impl system::Trait for Test {
 		type Origin = Origin;
-		type Index = u64;
+		type Index = ::Index;
 		type BlockNumber = u64;
 		type Hash = H256;
 		type Hashing = BlakeTwo256;
-		type Digest = Digest;
-		type AccountId = u64;
-		type Header = Header;
+		type Digest = generic::Digest<::Log>;
+		type AccountId = ::AccountId;
+		type Header = ::Header;
 		type Event = ();
-		type Log = DigestItem;
+		type Log = ::Log;
 	}
 	impl session::Trait for Test {
-		type ConvertAccountIdToSessionKey = Identity;
+		type ConvertAccountIdToSessionKey = ::SessionKeyConversion;
 		type OnSessionChange = ();
 		type Event = ();
 	}
@@ -305,14 +496,25 @@ mod tests {
 
 	fn new_test_ext(parachains: Vec<(Id, Vec<u8>, Vec<u8>)>) -> TestExternalities<Blake2Hasher> {
 		let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
+		let authority_keys = [
+			Keyring::Alice,
+			Keyring::Bob,
+			Keyring::Charlie,
+			Keyring::Dave,
+			Keyring::Eve,
+			Keyring::Ferdie,
+			Keyring::One,
+			Keyring::Two,
+		];
+
 		t.extend(consensus::GenesisConfig::<Test>{
 			code: vec![],
-			authorities: vec![1, 2, 3],
+			authorities: authority_keys.iter().map(|k| k.to_raw_public().into()).collect(),
 			_genesis_phantom_data: PhantomData,
 		}.build_storage().unwrap().0);
 		t.extend(session::GenesisConfig::<Test>{
 			session_length: 1000,
-			validators: vec![1, 2, 3, 4, 5, 6, 7, 8],
+			validators: authority_keys.iter().map(|k| k.to_raw_public().into()).collect(),
 			_genesis_phantom_data: PhantomData,
 		}.build_storage().unwrap().0);
 		t.extend(GenesisConfig::<Test>{
@@ -322,6 +524,55 @@ mod tests {
 		t.into()
 	}
 
+	fn make_attestations(candidate: &mut AttestedCandidate) {
+		let mut vote_implicit = false;
+		let parent_hash = ::System::parent_hash();
+
+		let duty_roster = Parachains::calculate_duty_roster();
+		let candidate_hash = candidate.candidate.hash();
+
+		let authorities = ::Consensus::authorities();
+		let extract_key = |public: SessionKey| {
+			Keyring::from_raw_public(public.0).unwrap()
+		};
+
+		let validation_entries = duty_roster.validator_duty.iter()
+			.enumerate()
+			.map(|(i, d)| (i, d, true));
+
+		let availability_entries = duty_roster.guarantor_duty.iter()
+			.enumerate()
+			.map(|(i, d)| (i, d, false));
+
+		for (idx, &duty, is_validation) in validation_entries.chain(availability_entries) {
+			if duty != Chain::Parachain(candidate.parachain_index()) { continue }
+			if is_validation { vote_implicit = !vote_implicit };
+
+			let key = extract_key(authorities[idx]);
+
+			let statement = if is_validation && vote_implicit {
+				Statement::Candidate(candidate.candidate.clone())
+			} else if is_validation {
+				Statement::Valid(candidate_hash.clone())
+			} else {
+				Statement::Available(candidate_hash.clone())
+			};
+
+			let payload = localized_payload(statement, parent_hash);
+			let signature = key.sign(&payload[..]).into();
+
+			if is_validation {
+				candidate.validity_votes.push((authorities[idx], if vote_implicit {
+					ValidityAttestation::Implicit(signature)
+				} else {
+					ValidityAttestation::Explicit(signature)
+				}));
+			} else {
+				candidate.availability_votes.push((authorities[idx], signature));
+			}
+		}
+	}
+
 	#[test]
 	fn active_parachains_should_work() {
 		let parachains = vec![
@@ -397,4 +648,130 @@ mod tests {
 			assert!(duty_roster_1 != duty_roster_2);
 		});
 	}
+
+	#[test]
+	fn unattested_candidate_is_rejected() {
+		let parachains = vec![
+			(0u32.into(), vec![], vec![]),
+			(1u32.into(), vec![], vec![]),
+		];
+
+		with_externalities(&mut new_test_ext(parachains), || {
+			system::Module::<Test>::set_random_seed([0u8; 32].into());
+			let candidate = AttestedCandidate {
+				validity_votes: vec![],
+				availability_votes: vec![],
+				candidate: CandidateReceipt {
+					parachain_index: 0.into(),
+					collator: Default::default(),
+					signature: Default::default(),
+					head_data: HeadData(vec![1, 2, 3]),
+					balance_uploads: vec![],
+					egress_queue_roots: vec![],
+					fees: 0,
+					block_data_hash: Default::default(),
+				}
+			};
+
+			assert!(Parachains::dispatch(Call::set_heads(vec![candidate]), Origin::INHERENT).is_err());
+		})
+	}
+
+	#[test]
+	fn attested_candidates_accepted_in_order() {
+		let parachains = vec![
+			(0u32.into(), vec![], vec![]),
+			(1u32.into(), vec![], vec![]),
+		];
+
+		with_externalities(&mut new_test_ext(parachains), || {
+			system::Module::<Test>::set_random_seed([0u8; 32].into());
+			let mut candidate_a = AttestedCandidate {
+				validity_votes: vec![],
+				availability_votes: vec![],
+				candidate: CandidateReceipt {
+					parachain_index: 0.into(),
+					collator: Default::default(),
+					signature: Default::default(),
+					head_data: HeadData(vec![1, 2, 3]),
+					balance_uploads: vec![],
+					egress_queue_roots: vec![],
+					fees: 0,
+					block_data_hash: Default::default(),
+				}
+			};
+
+			let mut candidate_b = AttestedCandidate {
+				validity_votes: vec![],
+				availability_votes: vec![],
+				candidate: CandidateReceipt {
+					parachain_index: 1.into(),
+					collator: Default::default(),
+					signature: Default::default(),
+					head_data: HeadData(vec![2, 3, 4]),
+					balance_uploads: vec![],
+					egress_queue_roots: vec![],
+					fees: 0,
+					block_data_hash: Default::default(),
+				}
+			};
+
+			make_attestations(&mut candidate_a);
+			make_attestations(&mut candidate_b);
+
+			assert!(Parachains::dispatch(
+				Call::set_heads(vec![candidate_b.clone(), candidate_a.clone()]),
+				Origin::INHERENT,
+			).is_err());
+
+			assert!(Parachains::dispatch(
+				Call::set_heads(vec![candidate_a.clone(), candidate_b.clone()]),
+				Origin::INHERENT,
+			).is_ok());
+		});
+	}
+
+	#[test]
+	fn duplicate_vote_is_rejected() {
+		let parachains = vec![
+			(0u32.into(), vec![], vec![]),
+			(1u32.into(), vec![], vec![]),
+		];
+
+		with_externalities(&mut new_test_ext(parachains), || {
+			system::Module::<Test>::set_random_seed([0u8; 32].into());
+			let mut candidate = AttestedCandidate {
+				validity_votes: vec![],
+				availability_votes: vec![],
+				candidate: CandidateReceipt {
+					parachain_index: 0.into(),
+					collator: Default::default(),
+					signature: Default::default(),
+					head_data: HeadData(vec![1, 2, 3]),
+					balance_uploads: vec![],
+					egress_queue_roots: vec![],
+					fees: 0,
+					block_data_hash: Default::default(),
+				}
+			};
+
+			make_attestations(&mut candidate);
+
+			let mut double_validity = candidate.clone();
+			double_validity.validity_votes.push(candidate.validity_votes[0].clone());
+
+			assert!(Parachains::dispatch(
+				Call::set_heads(vec![double_validity]),
+				Origin::INHERENT,
+			).is_err());
+
+			let mut double_availability = candidate.clone();
+			double_availability.availability_votes.push(candidate.availability_votes[0].clone());
+
+			assert!(Parachains::dispatch(
+				Call::set_heads(vec![double_availability]),
+				Origin::INHERENT,
+			).is_err());
+		});
+	}
 }
diff --git a/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock
index 5f6afdfae853734e17fa21b28171a8e22c972e01..0c7200296ab533faa6aff0344d27c3092bef354e 100644
--- a/polkadot/runtime/wasm/Cargo.lock
+++ b/polkadot/runtime/wasm/Cargo.lock
@@ -6,6 +6,11 @@ dependencies = [
  "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "bitvec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "byteorder"
 version = "1.2.7"
@@ -133,6 +138,7 @@ dependencies = [
 name = "polkadot-runtime"
 version = "0.1.0"
 dependencies = [
+ "bitvec 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "integer-sqrt 0.1.0 (git+https://github.com/paritytech/integer-sqrt-rs.git)",
  "parity-codec 2.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "parity-codec-derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -543,6 +549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [metadata]
 "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
+"checksum bitvec 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e37e2176261200377c7cde4c6de020394174df556c356f965e4bc239f5ce1c5a"
 "checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d"
 "checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda"
 "checksum crunchy 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c240f247c278fa08a6d4820a6a222bfc6e0d999e51ba67be94f44c905b2161f2"
diff --git a/polkadot/runtime/wasm/Cargo.toml b/polkadot/runtime/wasm/Cargo.toml
index a718d695a2d3e71c0dd6f58ab7771c194d2c34da..4282d32cea825cb2b06e171851a90f4fa554aa5c 100644
--- a/polkadot/runtime/wasm/Cargo.toml
+++ b/polkadot/runtime/wasm/Cargo.toml
@@ -7,6 +7,7 @@ authors = ["Parity Technologies <admin@parity.io>"]
 crate-type = ["cdylib"]
 
 [dependencies]
+bitvec = { version = "0.8", default-features = false, features = ["alloc"] }
 integer-sqrt = { git = "https://github.com/paritytech/integer-sqrt-rs.git", branch = "master" }
 polkadot-primitives = { path = "../../primitives", default-features = false }
 safe-mix = { version = "1.0", default-features = false }
diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm
index e70997545ae5be376d36c4f0470da48828fe3d50..cd5656aa78794e8e182d60bf220736079c6fdb4d 100644
Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ
diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm
index 665b570b511bc15ff96c2e413391cd88ab79ba7a..c5486a58a6db3fb32c497f79794b9730b080657f 100755
Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ
diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs
index 2a6c828f5b3254fd902614bd5ad3769157c16801..6deece66df4a5c53e86f7bcd8443c6967c5b1eb0 100644
--- a/polkadot/statement-table/src/generic.rs
+++ b/polkadot/statement-table/src/generic.rs
@@ -177,7 +177,7 @@ enum ValidityVote<S: Eq + Clone> {
 pub struct Summary<D, G> {
 	/// The digest of the candidate referenced.
 	pub candidate: D,
-	/// The group that candidate is in.
+	/// The group that the candidate is in.
 	pub group_id: G,
 	/// How many validity votes are currently witnessed.
 	pub validity_votes: usize,
@@ -187,6 +187,30 @@ pub struct Summary<D, G> {
 	pub signalled_bad: bool,
 }
 
+/// A validity attestation.
+#[derive(Clone, PartialEq, Decode, Encode)]
+pub enum ValidityAttestation<S> {
+	/// implicit validity attestation by issuing.
+	/// This corresponds to issuance of a `Candidate` statement.
+	Implicit(S),
+	/// An explicit attestation. This corresponds to issuance of a
+	/// `Valid` statement.
+	Explicit(S),
+}
+
+/// An attested-to candidate.
+#[derive(Clone, PartialEq, Decode, Encode)]
+pub struct AttestedCandidate<Group, Candidate, AuthorityId, Signature> {
+	/// The group ID that the candidate is in.
+	pub group_id: Group,
+	/// The candidate data.
+	pub candidate: Candidate,
+	/// Validity attestations.
+	pub validity_votes: Vec<(AuthorityId, ValidityAttestation<Signature>)>,
+	/// Availability attestations.
+	pub availability_votes: Vec<(AuthorityId, Signature)>
+}
+
 /// Stores votes and data about a candidate.
 pub struct CandidateData<C: Context> {
 	group_id: C::GroupId,
@@ -202,6 +226,53 @@ impl<C: Context> CandidateData<C> {
 		!self.indicated_bad_by.is_empty()
 	}
 
+	/// Yield a full attestation for a candidate.
+	/// If the candidate can be included, it will return `Some`.
+	pub fn attested(&self, validity_threshold: usize, availability_threshold: usize)
+		-> Option<AttestedCandidate<
+			C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
+		>>
+	{
+		if self.can_be_included(validity_threshold, availability_threshold) {
+			let validity_votes: Vec<_> = self.validity_votes.iter()
+				.filter_map(|(a, v)| match *v {
+					ValidityVote::Invalid(_) => None,
+
+					ValidityVote::Valid(ref s) =>
+						Some((a, ValidityAttestation::Explicit(s.clone()))),
+					ValidityVote::Issued(ref s) =>
+						Some((a, ValidityAttestation::Implicit(s.clone()))),
+				})
+				.take(validity_threshold)
+				.map(|(k, v)| (k.clone(), v.clone()))
+				.collect();
+
+			assert!(
+				validity_votes.len() == validity_threshold,
+				"candidate is includable; therefore there are enough validity votes; qed",
+			);
+
+			let availability_votes: Vec<_> = self.availability_votes.iter()
+				.take(availability_threshold)
+				.map(|(k, v)| (k.clone(), v.clone()))
+				.collect();
+
+			assert!(
+				availability_votes.len() == availability_threshold,
+				"candidate is includable; therefore there are enough availability votes; qed",
+			);
+
+			Some(AttestedCandidate {
+				group_id: self.group_id.clone(),
+				candidate: self.candidate.clone(),
+				validity_votes,
+				availability_votes,
+			})
+		} else {
+			None
+		}
+	}
+
 	// Candidate data can be included in a proposal
 	// if it has enough validity and availability votes
 	// and no authorities have called it bad.
@@ -267,7 +338,9 @@ impl<C: Context> Table<C> {
 	/// best candidate for each group with requisite votes for inclusion.
 	///
 	/// The vector is sorted in ascending order by group id.
-	pub fn proposed_candidates<'a>(&'a self, context: &C) -> Vec<&'a C::Candidate> {
+	pub fn proposed_candidates(&self, context: &C) -> Vec<AttestedCandidate<
+		C::GroupId, C::Candidate, C::AuthorityId, C::Signature,
+	>> {
 		use std::collections::BTreeMap;
 		use std::collections::btree_map::Entry as BTreeEntry;
 
@@ -282,19 +355,26 @@ impl<C: Context> Table<C> {
 			let (validity_t, availability_t) = context.requisite_votes(group_id);
 
 			if !candidate_data.can_be_included(validity_t, availability_t) { continue }
-			let candidate = &candidate_data.candidate;
 			match best_candidates.entry(group_id.clone()) {
+				BTreeEntry::Vacant(vacant) => {
+					vacant.insert((candidate_data, validity_t, availability_t));
+				},
 				BTreeEntry::Occupied(mut occ) => {
 					let candidate_ref = occ.get_mut();
-					if *candidate_ref > candidate {
-						*candidate_ref = candidate;
+					if candidate_ref.0.candidate > candidate_data.candidate {
+						candidate_ref.0 = candidate_data;
 					}
 				}
-				BTreeEntry::Vacant(vacant) => { vacant.insert(candidate); },
 			}
 		}
 
-		best_candidates.values().cloned().collect::<Vec<_>>()
+		best_candidates.values()
+			.map(|&(candidate_data, validity_t, availability_t)|
+				candidate_data.attested(validity_t, availability_t)
+					.expect("candidate has been checked includable; \
+						therefore an attestation can be constructed; qed")
+			)
+			.collect::<Vec<_>>()
 	}
 
 	/// Whether a candidate can be included.
diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs
index 3b10979fcd100d00a283f4eced2427a9492de156..91173aad680e28a23790d1b8f5297c0097849ffe 100644
--- a/polkadot/statement-table/src/lib.rs
+++ b/polkadot/statement-table/src/lib.rs
@@ -25,7 +25,9 @@ pub mod generic;
 
 pub use generic::Table;
 
-use primitives::parachain::{Id, CandidateReceipt, CandidateSignature as Signature};
+use primitives::parachain::{
+	Id, CandidateReceipt, CandidateSignature as Signature, Statement as PrimitiveStatement,
+};
 use primitives::{SessionKey, Hash};
 
 /// Statements about candidates on the network.
@@ -86,3 +88,14 @@ impl<C: Context> generic::Context for C {
 		Context::requisite_votes(self, group)
 	}
 }
+
+impl From<Statement> for PrimitiveStatement {
+	fn from(s: Statement) -> PrimitiveStatement {
+		match s {
+			generic::Statement::Valid(s) => PrimitiveStatement::Valid(s),
+			generic::Statement::Invalid(s) => PrimitiveStatement::Invalid(s),
+			generic::Statement::Candidate(s) => PrimitiveStatement::Candidate(s),
+			generic::Statement::Available(s) => PrimitiveStatement::Available(s),
+		}
+	}
+}