diff --git a/da/common.py b/da/common.py index 8d428e3..72388b2 100644 --- a/da/common.py +++ b/da/common.py @@ -39,6 +39,10 @@ BLSPrivateKey = int BLSSignature = bytes +class Bitfield(List[bool]): + pass + + @dataclass class Attestation: signature: BLSSignature @@ -47,6 +51,7 @@ class Attestation: @dataclass class Certificate: aggregated_signatures: BLSSignature + signers: Bitfield aggregated_column_commitment: Commitment row_commitments: List[Commitment] diff --git a/da/dispersal.py b/da/dispersal.py index 2967e74..aade4f9 100644 --- a/da/dispersal.py +++ b/da/dispersal.py @@ -4,7 +4,7 @@ from typing import List, Optional, Generator, Sequence from py_ecc.bls import G2ProofOfPossession as bls_pop -from da.common import Certificate, NodeId, BLSPublickey +from da.common import Certificate, NodeId, BLSPublickey, Bitfield from da.encoder import EncodedData from da.verifier import DABlob, Attestation @@ -19,6 +19,10 @@ class DispersalSettings: class Dispersal: def __init__(self, settings: DispersalSettings): self.settings = settings + # sort nodes_ids and related public keys + self.settings.nodes_ids, self.settings.nodes_pubkey = zip( + *sorted(zip(self.settings.nodes_ids, self.settings.nodes_pubkey), key=lambda x: x[0]) + ) def _prepare_data(self, encoded_data: EncodedData) -> Generator[DABlob, None, None]: assert len(encoded_data.column_commitments) == len(self.settings.nodes_ids) @@ -45,11 +49,18 @@ class Dispersal: def _send_and_await_response(self, node: NodeId, blob: DABlob) -> Optional[Attestation]: pass - def _build_certificate(self, encoded_data: EncodedData, attestations: Sequence[Attestation]) -> Certificate: + def _build_certificate( + self, + encoded_data: EncodedData, + attestations: Sequence[Attestation], + signers: Bitfield + ) -> Certificate: assert len(attestations) >= self.settings.threshold + assert len(attestations) == signers.count(True) aggregated = bls_pop.Aggregate([attestation.signature for attestation in attestations]) return Certificate( aggregated_signatures=aggregated, + signers=signers, aggregated_column_commitment=encoded_data.aggregated_column_commitment, row_commitments=encoded_data.row_commitments ) @@ -69,9 +80,18 @@ class Dispersal: def disperse(self, encoded_data: EncodedData) -> Optional[Certificate]: attestations = [] attested_message = self._build_attestation_message(encoded_data) - for node, pk, blob in zip(self.settings.nodes_ids, self.settings.nodes_pubkey, self._prepare_data(encoded_data)): + signed = Bitfield(False for _ in range(len(self.settings.nodes_ids))) + blob_data = zip( + range(len(self.settings.nodes_ids)), + self.settings.nodes_ids, + self.settings.nodes_pubkey, + self._prepare_data(encoded_data) + ) + for i, node, pk, blob in blob_data: if attestation := self._send_and_await_response(node, blob): if self._verify_attestation(pk, attested_message, attestation): + # mark as received + signed[i] = True attestations.append(attestation) if len(attestations) >= self.settings.threshold: - return self._build_certificate(encoded_data, attestations) + return self._build_certificate(encoded_data, attestations, signed) diff --git a/da/test_dispersal.py b/da/test_dispersal.py index 5895b2c..dabe830 100644 --- a/da/test_dispersal.py +++ b/da/test_dispersal.py @@ -4,7 +4,7 @@ from unittest import TestCase from .encoder import DAEncoderParams, DAEncoder from .test_encoder import TestEncoder -from da.common import NodeId, Attestation +from da.common import NodeId, Attestation, Bitfield from da.dispersal import Dispersal, EncodedData, DispersalSettings from py_ecc.bls import G2ProofOfPossession as bls_pop @@ -28,7 +28,7 @@ class TestDispersal(TestCase): def test_build_certificate_insufficient_attestations(self): with self.assertRaises(AssertionError): - self.dispersal._build_certificate(None, []) + self.dispersal._build_certificate(None, [], []) def test_build_certificate_enough_attestations(self): mock_encoded_data = EncodedData( @@ -36,7 +36,11 @@ class TestDispersal(TestCase): ) mock_message = sha3_256(mock_encoded_data.aggregated_column_commitment).digest() mock_attestations = [Attestation(bls_pop.Sign(sk, mock_message)) for sk in self.secret_keys] - certificate = self.dispersal._build_certificate(mock_encoded_data, mock_attestations) + certificate = self.dispersal._build_certificate( + mock_encoded_data, + mock_attestations, + Bitfield([True for _ in range(len(self.secret_keys))]) + ) self.assertIsNotNone(certificate) self.assertEqual(certificate.aggregated_column_commitment, mock_encoded_data.aggregated_column_commitment) self.assertEqual(certificate.row_commitments, []) @@ -67,4 +71,8 @@ class TestDispersal(TestCase): certificate.aggregated_signatures ) ) + self.assertEqual( + certificate.signers, + [True if i < self.dispersal.settings.threshold else False for i in range(self.n_nodes)] + )