referrerpolicy=no-referrer-when-downgrade

sp_consensus_beefy/
witness.rs

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