Skip to main content

subsoil/consensus/beefy/
witness.rs

1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Primitives for light, 2-phase interactive verification protocol.
8//!
9//! Instead of submitting full list of signatures, it's possible to submit first a witness
10//! form of [SignedCommitment].
11//! This can later be verified by the client requesting only some (out of all) signatures for
12//! verification. This allows lowering the data and computation cost of verifying the
13//! signed commitment.
14
15use super::commitment::{Commitment, SignedCommitment};
16use alloc::vec::Vec;
17
18/// A light form of [SignedCommitment].
19///
20/// This is a light ("witness") form of the signed commitment. Instead of containing full list of
21/// signatures, which might be heavy and expensive to verify, it only contains a bit vector of
22/// validators which signed the original [SignedCommitment] and a merkle root of all signatures.
23///
24/// This can be used by light clients for 2-phase interactive verification (for instance for
25/// Ethereum Mainnet), in a commit-reveal like scheme, where first we submit only the signed
26/// commitment witness and later on, the client picks only some signatures to verify at random.
27#[derive(Debug, PartialEq, Eq, codec::Encode, codec::Decode)]
28pub struct SignedCommitmentWitness<TBlockNumber, TSignatureAccumulator> {
29	/// The full content of the commitment.
30	pub commitment: Commitment<TBlockNumber>,
31
32	/// The bit vector of validators who signed the commitment.
33	pub signed_by: Vec<bool>, // TODO [ToDr] Consider replacing with bitvec crate
34
35	/// Either a merkle root of signatures in the original signed commitment or a single aggregated
36	/// BLS signature aggregating all original signatures.
37	pub signature_accumulator: TSignatureAccumulator,
38}
39
40impl<TBlockNumber, TSignatureAccumulator>
41	SignedCommitmentWitness<TBlockNumber, TSignatureAccumulator>
42{
43	/// Convert [SignedCommitment] into [SignedCommitmentWitness].
44	///
45	/// This takes a [SignedCommitment], which contains full signatures
46	/// and converts it into a witness form, which does not contain full signatures,
47	/// only a bit vector indicating which validators have signed the original [SignedCommitment]
48	/// and a merkle root of all signatures.
49	///
50	/// Returns the full list of signatures along with the witness.
51	pub fn from_signed<TSignatureAggregator, TSignature>(
52		signed: SignedCommitment<TBlockNumber, TSignature>,
53		aggregator: TSignatureAggregator,
54	) -> (Self, Vec<Option<TSignature>>)
55	where
56		TSignatureAggregator: FnOnce(&[Option<TSignature>]) -> TSignatureAccumulator,
57	{
58		let SignedCommitment { commitment, signatures } = signed;
59		let signed_by = signatures.iter().map(|s| s.is_some()).collect();
60		let signature_accumulator = aggregator(&signatures);
61
62		(Self { commitment, signed_by, signature_accumulator }, signatures)
63	}
64}
65
66#[cfg(test)]
67mod tests {
68	use crate::core::Pair;
69	use crate::crypto_hashing::keccak_256;
70
71	use super::*;
72	use codec::Decode;
73
74	use crate::consensus::beefy::{
75		ecdsa_crypto::Signature as EcdsaSignature, known_payloads, Payload,
76	};
77
78	#[cfg(feature = "bls-experimental")]
79	use crate::consensus::beefy::bls_crypto::Signature as BlsSignature;
80
81	#[cfg(feature = "bls-experimental")]
82	use w3f_bls::{
83		single_pop_aggregator::SignatureAggregatorAssumingPoP, Message, SerializableToBytes,
84		Signed, TinyBLS381,
85	};
86
87	type TestCommitment = Commitment<u128>;
88
89	// Types for ecdsa signed commitment.
90	type TestEcdsaSignedCommitment = SignedCommitment<u128, EcdsaSignature>;
91	type TestEcdsaSignedCommitmentWitness =
92		SignedCommitmentWitness<u128, Vec<Option<EcdsaSignature>>>;
93
94	#[cfg(feature = "bls-experimental")]
95	#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
96	struct EcdsaBlsSignaturePair(EcdsaSignature, BlsSignature);
97
98	// types for commitment containing  bls signature along side ecdsa signature
99	#[cfg(feature = "bls-experimental")]
100	type TestBlsSignedCommitment = SignedCommitment<u128, EcdsaBlsSignaturePair>;
101	#[cfg(feature = "bls-experimental")]
102	type TestBlsSignedCommitmentWitness = SignedCommitmentWitness<u128, Vec<u8>>;
103
104	// The mock signatures are equivalent to the ones produced by the BEEFY keystore
105	fn mock_ecdsa_signatures() -> (EcdsaSignature, EcdsaSignature) {
106		let alice = crate::core::ecdsa::Pair::from_string("//Alice", None).unwrap();
107
108		let msg = keccak_256(b"This is the first message");
109		let sig1 = alice.sign_prehashed(&msg);
110
111		let msg = keccak_256(b"This is the second message");
112		let sig2 = alice.sign_prehashed(&msg);
113
114		(sig1.into(), sig2.into())
115	}
116
117	// Generates mock aggregatable bls signature for generating test commitment
118	// BLS signatures
119	#[cfg(feature = "bls-experimental")]
120	fn mock_bls_signatures() -> (BlsSignature, BlsSignature) {
121		let alice = crate::core::bls::Pair::from_string("//Alice", None).unwrap();
122
123		let msg = b"This is the first message";
124		let sig1 = alice.sign(msg);
125
126		let msg = b"This is the second message";
127		let sig2 = alice.sign(msg);
128
129		(sig1.into(), sig2.into())
130	}
131
132	fn ecdsa_signed_commitment() -> TestEcdsaSignedCommitment {
133		let payload = Payload::from_single_entry(
134			known_payloads::MMR_ROOT_ID,
135			"Hello World!".as_bytes().to_vec(),
136		);
137		let commitment: TestCommitment =
138			Commitment { payload, block_number: 5, validator_set_id: 0 };
139
140		let sigs = mock_ecdsa_signatures();
141
142		SignedCommitment { commitment, signatures: vec![None, None, Some(sigs.0), Some(sigs.1)] }
143	}
144
145	#[cfg(feature = "bls-experimental")]
146	fn ecdsa_and_bls_signed_commitment() -> TestBlsSignedCommitment {
147		let payload = Payload::from_single_entry(
148			known_payloads::MMR_ROOT_ID,
149			"Hello World!".as_bytes().to_vec(),
150		);
151		let commitment: TestCommitment =
152			Commitment { payload, block_number: 5, validator_set_id: 0 };
153
154		let ecdsa_sigs = mock_ecdsa_signatures();
155		let bls_sigs = mock_bls_signatures();
156
157		SignedCommitment {
158			commitment,
159			signatures: vec![
160				None,
161				None,
162				Some(EcdsaBlsSignaturePair(ecdsa_sigs.0, bls_sigs.0)),
163				Some(EcdsaBlsSignaturePair(ecdsa_sigs.1, bls_sigs.1)),
164			],
165		}
166	}
167
168	#[test]
169	fn should_convert_signed_commitment_to_witness() {
170		// given
171		let signed = ecdsa_signed_commitment();
172
173		// when
174		let (witness, signatures) =
175			TestEcdsaSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
176
177		// then
178		assert_eq!(witness.signature_accumulator, signatures);
179	}
180
181	#[test]
182	#[cfg(feature = "bls-experimental")]
183	fn should_convert_dually_signed_commitment_to_witness() {
184		// given
185		let signed = ecdsa_and_bls_signed_commitment();
186
187		// when
188		let (witness, _signatures) =
189			// from signed take a function as the aggregator 
190			TestBlsSignedCommitmentWitness::from_signed::<_, _>(signed, |sigs| {
191				// we are going to aggregate the signatures here
192				let mut aggregatedsigs: SignatureAggregatorAssumingPoP<TinyBLS381> =
193					SignatureAggregatorAssumingPoP::new(Message::new(b"", b"mock payload"));
194
195				for sig in sigs {
196					match sig {
197						Some(sig) => {
198							let serialized_sig : Vec<u8> = (*sig.1).to_vec();
199							aggregatedsigs.add_signature(
200								&w3f_bls::Signature::<TinyBLS381>::from_bytes(
201									serialized_sig.as_slice()
202								).unwrap()
203							);
204						},
205						None => (),
206					}
207				}
208				(&aggregatedsigs).signature().to_bytes()
209			});
210
211		// We can't use BlsSignature::try_from because it expected 112Bytes (CP (64) + BLS 48)
212		// single signature while we are having a BLS aggregated signature corresponding to no CP.
213		w3f_bls::Signature::<TinyBLS381>::from_bytes(witness.signature_accumulator.as_slice())
214			.unwrap();
215	}
216
217	#[test]
218	fn should_encode_and_decode_witness() {
219		// Given
220		let signed = ecdsa_signed_commitment();
221		let (witness, _) = TestEcdsaSignedCommitmentWitness::from_signed::<_, _>(
222			signed,
223			|sigs: &[std::option::Option<EcdsaSignature>]| sigs.to_vec(),
224		);
225
226		// When
227		let encoded = codec::Encode::encode(&witness);
228		let decoded = TestEcdsaSignedCommitmentWitness::decode(&mut &*encoded);
229
230		// Then
231		assert_eq!(decoded, Ok(witness));
232		assert_eq!(
233			encoded,
234			array_bytes::hex2bytes_unchecked(
235				"\
236				046d683048656c6c6f20576f726c642105000000000000000000000000000000000000000000000010\
237				0000010110000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c\
238				746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a8\
239				6cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487b\
240				ca2324b6a0046395a71681be3d0c2a00\
241			"
242			)
243		);
244	}
245}