diff --git a/eth-types/src/spec.rs b/eth-types/src/spec.rs index 79f492b5..06bd3ccb 100644 --- a/eth-types/src/spec.rs +++ b/eth-types/src/spec.rs @@ -28,13 +28,12 @@ pub trait Spec: 'static + Sized + Copy + Default + Debug { } } -/// Ethereum Foundation specifications. #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] pub struct Test; impl Spec for Test { const VALIDATOR_REGISTRY_LIMIT: usize = 100; - const MAX_VALIDATORS_PER_COMMITTEE: usize = 5; + const MAX_VALIDATORS_PER_COMMITTEE: usize = 10; const MAX_COMMITTEES_PER_SLOT: usize = 1; const SLOTS_PER_EPOCH: usize = 1; const VALIDATOR_0_GINDEX: usize = 94557999988736; @@ -48,6 +47,25 @@ impl Spec for Test { type SiganturesCurve = bls12_381::G2; } +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +pub struct Sepolia; + +impl Spec for Sepolia { + const VALIDATOR_REGISTRY_LIMIT: usize = 100; + const MAX_VALIDATORS_PER_COMMITTEE: usize = 10; + const MAX_COMMITTEES_PER_SLOT: usize = 10; + const SLOTS_PER_EPOCH: usize = 1; + const VALIDATOR_0_GINDEX: usize = 94557999988736; + const STATE_TREE_DEPTH: usize = 51; + const STATE_TREE_LEVEL_PUBKEYS: usize = Self::STATE_TREE_DEPTH; + const STATE_TREE_LEVEL_VALIDATORS: usize = Self::STATE_TREE_LEVEL_PUBKEYS - 1; + const STATE_TREE_LEVEL_BEACON_STATE: usize = 6; + const DST: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + + type PubKeysCurve = bls12_381::G1; + type SiganturesCurve = bls12_381::G2; +} + #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] pub struct Mainnet; diff --git a/preprocessor/scripts/generateInputData.ts b/preprocessor/scripts/generateInputData.ts index 86ddd945..d0914d28 100644 --- a/preprocessor/scripts/generateInputData.ts +++ b/preprocessor/scripts/generateInputData.ts @@ -1,5 +1,5 @@ import fs from "fs"; -import { bls12_381 } from '@noble/curves/bls12-381'; +import { bls12_381 } from '@noble/curves/bls12-381' import { BitArray, ContainerType, @@ -10,9 +10,9 @@ import { ssz, } from "@lodestar/types" import { createProof, ProofType, MultiProof, Node } from "@chainsafe/persistent-merkle-tree"; -import { g1PointToLeBytes as g1PointToBytesLE, g2PointToLeBytes, serialize } from "./util"; +import { chunkArray, g1PointToLeBytes as g1PointToBytesLE, g2PointToLeBytes, serialize } from "./util"; import { createNodeFromMultiProofWithTrace, printTrace } from "./merkleTrace"; -import { hexToBytes, bytesToHex } from "@noble/curves/abstract/utils"; +import { hexToBytes, bytesToHex, numberToBytesBE } from "@noble/curves/abstract/utils"; import { ProjPointType } from "@noble/curves/abstract/weierstrass"; import { createNodeFromCompactMultiProof } from "@chainsafe/persistent-merkle-tree/lib/proof/compactMulti"; import { ValidatorsSsz, Validator, BeaconStateSsz } from "./types"; @@ -21,21 +21,16 @@ const DST = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; console.log("VALIDATOR_0_GINDEX:", BeaconStateSsz.getPathInfo(['validators', 0]).gindex); -const N = 5; +const N_validators = parseInt(process.argv[2]) || 5; +const N_committees = parseInt(process.argv[3]) || 1; let gindices: bigint[] = []; let validatorBaseGindices: bigint[] = []; let nonRlcGindices: bigint[] = []; -let privKeyHexes = [ - "5644920314564b11404384380c1d677871ada2ec9470d5f43f03aa931ecef54b", - "4314d9849e5cb4add3579426ba8833621dcfeba8f8b33ec8779e76c8facf6b6a", - "6d973b68057d1b01425eb705d9951cf725aa4f138ded6d56fca23d03b7200575", - "3ae65153efe6e1103561cc672aa0044784df0244bf9cae8489fe9ab93120ee70", - "078384584ee0800afb493de39a95955be2132f21fdbf82d2c35a603846cb4cc8" -]; +let privKeyHexes: string[] = JSON.parse(fs.readFileSync("../test_data/private_keys.json").toString()); -const target_epoch = 25; +const targetEpoch = 25; //----------------- Beacon state -----------------// @@ -46,9 +41,9 @@ beaconState.validators = []; let pubKeyPoints: ProjPointType[] = []; -for (let i = 0; i < N; i++) { +for (let i = 0; i < N_validators * N_committees; i++) { // use 5 pregenerated private keys to avoid changing JSON files. - let privKey = i < 5 ? hexToBytes(privKeyHexes[i]) : bls12_381.utils.randomPrivateKey(); + let privKey = i < privKeyHexes.length ? hexToBytes(privKeyHexes[i]) : bls12_381.utils.randomPrivateKey(); let p = bls12_381.G1.ProjectivePoint.fromPrivateKey(privKey); let pubkey = g1PointToBytesLE(p, true); @@ -83,8 +78,8 @@ fs.writeFileSync( serialize(Array.from(beaconState.validators.entries()).map(([i, validator]) => ({ id: i, shufflePos: i, - committee: 0, - isActive: !validator.slashed && validator.activationEpoch <= target_epoch && target_epoch < validator.exitEpoch, + committee: Math.floor(i / N_validators), + isActive: !validator.slashed && validator.activationEpoch <= targetEpoch && targetEpoch < validator.exitEpoch, isAttested: true, pubkey: Array.from(validator.pubkey), pubkeyUncompressed: Array.from(g1PointToBytesLE(pubKeyPoints[i], false)), @@ -96,14 +91,16 @@ fs.writeFileSync( }))) ); -//----------------- Committees -----------------// +fs.writeFileSync( + `../test_data/private_keys.json`, + serialize(privKeyHexes) +); -const aggregatedPubKey = bls12_381.aggregatePublicKeys(pubKeyPoints); -const aggPubkeyBytes = g1PointToBytesLE(aggregatedPubKey, false); -let bytesPubkeys = [ - Array.from(aggPubkeyBytes), -]; +//----------------- Committees -----------------// +const committeePubkeys = chunkArray(pubKeyPoints, N_validators); +const aggregatedPubKeys = committeePubkeys.map((pubKeys) => bls12_381.aggregatePublicKeys(pubKeys)); +let bytesPubkeys = aggregatedPubKeys.map((aggPubkey) => Array.from(g1PointToBytesLE(aggPubkey, false))); fs.writeFileSync( `../test_data/aggregated_pubkeys.json`, @@ -115,44 +112,45 @@ fs.writeFileSync( type Attestations = ValueOf; let attestations: Attestations = []; -let data = { - slot: 0, - index: 0, - beaconBlockRoot: Uint8Array.from(Array(32).fill(0)), - source: { - epoch: target_epoch - 1, - root: Uint8Array.from(Array(32).fill(0)) - }, - target: { - epoch: target_epoch, - root: Uint8Array.from(Array(32).fill(0)) - } -}; - -let dataRoot = ssz.phase0.AttestationData.hashTreeRoot(data); - -let msgPoint = bls12_381.G2.ProjectivePoint.fromAffine(bls12_381.G2.hashToCurve(dataRoot, { - DST: DST, -}).toAffine()); - -let signatures = []; -for (const privKey of privKeyHexes) { - const sigPoint = msgPoint.multiply(BigInt('0x' + privKey)); - signatures.push(sigPoint); +const beaconStateRoot = BeaconStateSsz.hashTreeRoot(beaconState); +const committeePrivKeys = chunkArray(privKeyHexes, N_validators); + +for (let i = 0; i < N_committees; i++) { + let data = { + slot: 32, + index: i, + beaconBlockRoot: Uint8Array.from(Array(32).fill(0)), + source: { + epoch: targetEpoch - 1, + root: Uint8Array.from(Array(32).fill(0)) + }, + target: { + epoch: targetEpoch, + root: beaconStateRoot, + } + }; + + let dataRoot = ssz.phase0.AttestationData.hashTreeRoot(data); + + let msgPoint = bls12_381.G2.ProjectivePoint.fromAffine(bls12_381.G2.hashToCurve(dataRoot, { + DST: DST, + }).toAffine()); + + let signatures = committeePrivKeys[i].map((privKey) => msgPoint.multiply(BigInt('0x' + privKey))); + let aggSignature = bls12_381.aggregateSignatures(signatures); + + // assert signature is valid + bls12_381.verify(aggSignature, msgPoint, aggregatedPubKeys[i]); + + let sigBytes = g2PointToLeBytes(aggSignature, true); + + attestations.push({ + aggregationBits: BitArray.fromBoolArray(Array(N_validators).fill(1)), + data: data, + signature: sigBytes + }); } -let signature = bls12_381.aggregateSignatures(signatures) - -// assert signature is valid -bls12_381.verify(signature, msgPoint, aggregatedPubKey); - -let sigBytes = g2PointToLeBytes(signature, true); - -attestations.push({ - aggregationBits: BitArray.fromBoolArray(Array(N).fill(1)), - data: data, - signature: sigBytes -}); let attestationJson = ssz.phase0.BeaconBlockBody.fields.attestations.toJson(attestations); diff --git a/preprocessor/scripts/util.ts b/preprocessor/scripts/util.ts index d6b803b2..fb248753 100644 --- a/preprocessor/scripts/util.ts +++ b/preprocessor/scripts/util.ts @@ -257,3 +257,11 @@ function bigIntToBytesLE(value: bigint): ArrayBuffer { return buffer; } + +export function chunkArray(arr: T[], chunkSize: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < arr.length; i += chunkSize) { + chunks.push(arr.slice(i, i + chunkSize)); + } + return chunks; +} diff --git a/test_data/attestations.json b/test_data/attestations.json index ac884770..8d65a4b5 100644 --- a/test_data/attestations.json +++ b/test_data/attestations.json @@ -1 +1 @@ -[{"aggregation_bits":"0x20","data":{"slot":"0","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"24","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"25","root":"0x0000000000000000000000000000000000000000000000000000000000000000"}},"signature":"0xc4ed3cb07dea6af7e29d2325e6ca014fcf6087f14aff95077db1f44ab3c559a28eced6412b407cb39a75901d1b7d410bc8ebc51a39e4400d82d26df488836e58dcd85dd9d31e2cda88ca82d83cd244cd45db6501f17a436f41e9156f05b72a85"}] \ No newline at end of file +[{"aggregation_bits":"0x20","data":{"slot":"32","index":"0","beacon_block_root":"0x0000000000000000000000000000000000000000000000000000000000000000","source":{"epoch":"24","root":"0x0000000000000000000000000000000000000000000000000000000000000000"},"target":{"epoch":"25","root":"0x77bc60ef873b1c2189bd695615bef4b1a4ce6e7749f70d84be36b5b3aff8bdbd"}},"signature":"0x2a0fb48e038894574031eb316b9b7cc5a9d2397862bc185a032be5fd1f76f31b697090a2c9c112ec14f4d71416517304c8c293691cd7ea70f415c095bdc800c22481048d7f57f83ac2c5087947683aa3241863e862f569e6da620e7e3d82cf82"}] \ No newline at end of file diff --git a/test_data/private_keys.json b/test_data/private_keys.json new file mode 100644 index 00000000..4d8d6f2b --- /dev/null +++ b/test_data/private_keys.json @@ -0,0 +1 @@ +["5644920314564b11404384380c1d677871ada2ec9470d5f43f03aa931ecef54b","4314d9849e5cb4add3579426ba8833621dcfeba8f8b33ec8779e76c8facf6b6a","6d973b68057d1b01425eb705d9951cf725aa4f138ded6d56fca23d03b7200575","3ae65153efe6e1103561cc672aa0044784df0244bf9cae8489fe9ab93120ee70","078384584ee0800afb493de39a95955be2132f21fdbf82d2c35a603846cb4cc8","59d25ba11238f35255602fcf7ac83b9837b1fbe7e689035c768c54ca4a3ce282","195f06da64e1e2ff662946bd9d7629a190dc497c426b044b887f9f7c3ecdc0ea","300d4aca21cba9142d6b5b3e9d849544f388d34dc6ba0fe777e613af916133b4","60da23b0c5c98767bf584f07bce20e044889ca5bafddbb35b45a306b39447ed0","000650b0abaaad66fe96255f35d31f88e12cd0be91dd62a9d9d9156ef0e8d0c0","2df434973bd420f17ec7d4e587267bed171a80393ff8d4ca3f09a2bb6d31608c","0ba9a63db43f9646f2b201bde96a09b94740df95dd47975e1f300a793c26a059","0cdbbc50e2ce09a14ec1007742d9273608f6d9e8fca2e34067896cb5bf83db07","3f6a7202ffeb9be2216601cace8e0a44be13cb7741d39c78a820ccd7ce98b4f1","718768440b75c5496c8179161cca6b75ab8c038586dee1c1be6ab7420a2f4f24","5672758099f7e0adb0fb5a942057ea6a51ddee05cf2fb25864be0867108a5e35","43a77bcbf8fc1dda16f338dbb64a095a6004490019b20676108905d918d09e5d","1094db7ed6b30287b14a2a270778616ad36cea0f8c596d0fd66e1967d16cb675","4d01394df040462b56ef12eb19e882f55d187ed78c80b22d632906dc5460c98b","16f6f7948e34c2a813278903187274a8c478bca7386c36c4d41765cf3d2ec112","1b66d10e405a196e5f4d8513caaf7fe54e02a467cd1201d0f8698f33d5aeebe7","14eb6118b4d84d36a0d4f89dd6b5cdfc326aa648d3ab266ab711b4d767d77786","140f0e7bb6b5ae3ad718b2b5c58ef650c549487cb2c18256a053e3d2ffcefdc4","36d5975c9a46fdaceacbb744378ab242365a2571b9c82a2c0edfbeb6ae60d64e","3e00e0ca0ad28501fd5071860266caf82f934908e5da80355ee17bca9f58cbc3","4b72aae8fde12fd0c15ff9028fcb0e7475f95abe30bd5ed822a74729597eaad8","34165281201282f6a8f0d0f7917fb401445d157838f2f586478f6f220211779b","2c53bdc8dc2c55d88a217441f223219d41af5e82dd39b0f14fd3da229e898554","1c5746663a0a5c0094c2ebc569fd5210335e88c5e60b1fc0f03f20377cd297e4","53ccc622d2e632485375d2bfa6018b2c8a2bbcb35bdd30df8de0479bb6e619f5","0fe3a14ec1cc214c51fac82bb9d2304f9ed64c41f6b7be796b1c0a5693a88c11","2c1933d8e4a425d9a01c590c4a88c111e1f59626402c82fbf33662839a3604d4","0ff76e507e4833e6058f3daf0143c4ff95976578e9a9e620bbf1b2030d0e47e4","3b8ebecece2b7a2c603c50c9183b286c9c2a18baae023d8d1ed5ff301cb84a38","53c472d80f5b5c802fb1acb398dbd61ee311838462416887a1a23af1d15773ae","0d4f1fa4bbce8d72b5e31b76ce37dc90bbe35b25cc422b71a872ff6955954daa","631cce4e6488aac450cb03899f6e63420592d7d36aca387358c71f13f7e047c9","65c24de0bfbc3775bde749d4f6ac75ffb6639f75b22b33b772c5f40df22ff754","6155edc45095f930006c792697a02bfaaf2870d71a0a9ef37dff45b48e6aa7e6","3eb3cb7e504d416a26215bf0cb839d3a55eed80a4ba4849524c8d3e4e65a8442","31635c71b7f74a16616882f12a7669714cf81444b09723d3fe7196c88edd3ec0","44bdc784b50438b28ba0eaafebd0cb898c7f71f54869f6c15d8baaba3324e1dc","3a58e1b61c2a3efbc3e1bea572f3fa9701865713da74f6679be921a600782f9e","5290d6dce47afabd3fffda909edea31ba02c838b47bc6ea16208961c5bc6bae5","1b86ef2bf007fd7aa1731b28d3a7dc7e980a555d2cc92093149fea1d29907627","3180f2c41bba02d89b3fce0dcae16a05bea902bb1391b041a7cf0f4e9c89b67a","6954f93bba6f226efeeb26c9980b478872693b381643665b7c6b7509601a9c01","2fe35a78c11b02f306d4c40f934b3b3ac89c1a71018dd3b979d71c43978fc065","6b1b36b62699782796da374de9227b3c40d1e1293b0a284d483392ada1e843f9","3fb278a452b070a4512c526dd75f95ab1e9e3065337fba8d570d254f633634f2"] \ No newline at end of file diff --git a/zkcasper-circuits/src/aggregation_circuit.rs b/zkcasper-circuits/src/aggregation_circuit.rs index a62520d7..9606cdcb 100644 --- a/zkcasper-circuits/src/aggregation_circuit.rs +++ b/zkcasper-circuits/src/aggregation_circuit.rs @@ -146,6 +146,7 @@ where // check that the assigned compressed encoding of pubkey used for constructiong affine point // is consistent with bytes used in validators table // assumption: order of self.validators is same as in BeaconState.validators so we treat iterator index as validator id + println!("pubkey_cells.len(): {}", pubkey_cells.len()); for (i, assigned_rlc) in pubkey_rlcs.into_iter().enumerate() { // convert halo2lib `AssignedValue` into vanilla Halo2 `Cell` let cells = assigned_rlc @@ -157,6 +158,7 @@ where .map(|&(cell, _)| cell); // get the corresponding cached cells from the validators table + println!("i: {}", i); let vs_table_cells = pubkey_cells.get(i).expect("pubkey cells for validator id"); @@ -259,6 +261,8 @@ impl<'a, F: Field, S: Spec + Sync> AggregationCircuitBuilder<'a, S, F> { .zip(self.validators_y.iter()) .group_by(|v| v.0.committee) .into_iter() + .sorted_by_key(|(committee, _)| *committee) + .take(S::MAX_COMMITTEES_PER_SLOT * S::SLOTS_PER_EPOCH) .map(|(_, g)| g.into_iter().collect_vec()) .collect_vec(); diff --git a/zkcasper-circuits/src/attestations_circuit.rs b/zkcasper-circuits/src/attestations_circuit.rs index 3fe8f4af..993382f9 100644 --- a/zkcasper-circuits/src/attestations_circuit.rs +++ b/zkcasper-circuits/src/attestations_circuit.rs @@ -6,9 +6,7 @@ use crate::{ HashToCurveCache, HashToCurveChip, Sha256Chip, }, sha256_circuit::{util::NUM_ROUNDS, Sha256CircuitConfig}, - util::{ - print_fq2_dev, Challenges, IntoWitness, SubCircuit, SubCircuitBuilder, SubCircuitConfig, - }, + util::{Challenges, IntoWitness, SubCircuit, SubCircuitBuilder, SubCircuitConfig}, witness::{self, Attestation, HashInput, HashInputChunk}, }; use eth_types::{AppCurveExt, Field, Spec}; @@ -128,10 +126,6 @@ where aggregated_pubkeys: Self::SynthesisArgs, ) -> Result<(), Error> { assert!(!self.attestations.is_empty(), "no attestations supplied"); - assert!( - self.attestations.len() <= S::MAX_COMMITTEES_PER_SLOT * S::SLOTS_PER_EPOCH, - "too many attestations supplied", - ); let mut first_pass = halo2_base::SKIP_FIRST_PASS; let range = RangeChip::default(config.range.lookup_bits()); @@ -164,8 +158,6 @@ where return Ok(()); } - let mut region = region; - let builder = &mut self.builder.borrow_mut(); let ctx = builder.main(0); @@ -195,7 +187,10 @@ where for Attestation:: { data, signature, .. - } in self.attestations.iter() + } in self + .attestations + .iter() + .take(S::MAX_COMMITTEES_PER_SLOT * S::SLOTS_PER_EPOCH) { assert!(!signature.is_infinity()); diff --git a/zkcasper-circuits/src/table/validators_table.rs b/zkcasper-circuits/src/table/validators_table.rs index 9ed74fe5..b717af5e 100644 --- a/zkcasper-circuits/src/table/validators_table.rs +++ b/zkcasper-circuits/src/table/validators_table.rs @@ -180,8 +180,9 @@ impl ValidatorsTable { let mut attest_digits_cells = vec![]; for (offset, row) in padded_validators .iter() - .flat_map(|&v| { + .flat_map(|(committee, v)| { v.table_assignment::( + *committee, challenge, &mut attest_digits, &mut committees_balances, diff --git a/zkcasper-circuits/src/validators_circuit.rs b/zkcasper-circuits/src/validators_circuit.rs index f5214a94..3264ef19 100644 --- a/zkcasper-circuits/src/validators_circuit.rs +++ b/zkcasper-circuits/src/validators_circuit.rs @@ -379,7 +379,7 @@ impl ValidatorsCircuitConfig { || Value::known(F::from(target_epoch)), )?; - if let Some(&validator) = padded_validators.get(i) { + if let Some((committee, validator)) = padded_validators.get(i) { target_gte_activation.assign( region, offset, @@ -394,6 +394,7 @@ impl ValidatorsCircuitConfig { )?; let validator_rows = validator.table_assignment::( + *committee, randomness, &mut attest_digits, &mut committees_balances, diff --git a/zkcasper-circuits/src/witness/validators.rs b/zkcasper-circuits/src/witness/validators.rs index 6a3c8373..7157a8db 100644 --- a/zkcasper-circuits/src/witness/validators.rs +++ b/zkcasper-circuits/src/witness/validators.rs @@ -51,10 +51,19 @@ impl Default for Validator { } impl Validator { + pub fn dummy(id: usize, committee: usize) -> Self { + Validator { + id, + committee, + ..Validator::default() + } + } + /// Get validaotor table record. /// `attest_digits` - digits composed from attestation bits of validator's committee. pub(crate) fn table_assignment( &self, + committee: usize, randomness: Value, attest_digits: &mut [Vec], committees_balances: &mut [u64], @@ -68,11 +77,11 @@ impl Validator { let attest_digit_len = S::attest_digits_len::(); let current_digit = committee_pos / F::NUM_BITS as usize; // accumulate bits into current digit - let committee_attest_digits = &mut attest_digits[self.committee]; + let committee_attest_digits = &mut attest_digits[committee]; let digit = committee_attest_digits.get_mut(current_digit).unwrap(); *digit = *digit * 2 + self.is_attested as u64; // accumulate balance of the current committee - committees_balances[self.committee] += self.effective_balance * self.is_active as u64; + committees_balances[committee] += self.effective_balance * self.is_active as u64; vec![ValidatorRow { id: Value::known(F::from(self.id as u64)), @@ -91,7 +100,7 @@ impl Validator { .take(attest_digit_len) .map(|b| Value::known(F::from(*b))) .collect(), - total_balance_acc: Value::known(F::from(committees_balances[self.committee])), + total_balance_acc: Value::known(F::from(committees_balances.iter().sum::())), }] } @@ -142,15 +151,25 @@ impl Validator { } } +/// Orders validators by committees and pads to S::MAX_VALIDATORS_PER_COMMITTEE. +/// Returns a vector of (committee, validator) pairs. +/// Note: use returned `committee` instead `validator.committee` +/// as `DUMMY_VALIDATOR` may contain wrong committee index. pub fn pad_to_max_per_committee<'a, S: Spec>( validators: impl Iterator, -) -> Vec<&'a Validator> { +) -> Vec<(usize, &'a Validator)> { validators .into_iter() .group_by(|v| v.committee) .into_iter() .sorted_by_key(|(committee, _)| *committee) - .flat_map(|(_, vs)| vs.pad_using(S::MAX_VALIDATORS_PER_COMMITTEE, |_| &DUMMY_VALIDATOR)) + .take(S::MAX_COMMITTEES_PER_SLOT * S::SLOTS_PER_EPOCH) + .flat_map(|(committee, vs)| { + vs.map(move |v| (committee, v)) + .pad_using(S::MAX_VALIDATORS_PER_COMMITTEE, move |_| { + (committee, &DUMMY_VALIDATOR) + }) + }) .collect() }