diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 270a512e..eb462911 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -42,6 +42,7 @@ jobs: uses: baptiste0928/cargo-install@v1 with: crate: cargo-machete + version: "=0.6.0" - run: cargo machete - name: Install cargo-sort uses: baptiste0928/cargo-install@v1 diff --git a/ferveo-python/examples/server_api_precomputed.py b/ferveo-python/examples/server_api_precomputed.py index 72263405..a9b98001 100644 --- a/ferveo-python/examples/server_api_precomputed.py +++ b/ferveo-python/examples/server_api_precomputed.py @@ -16,18 +16,16 @@ def gen_eth_addr(i: int) -> str: tau = 1 shares_num = 4 +validators_num = shares_num + 2 # In precomputed variant, security threshold must be equal to shares_num security_threshold = shares_num -validator_keypairs = [Keypair.random() for _ in range(0, shares_num)] +validator_keypairs = [Keypair.random() for _ in range(0, validators_num)] validators = [ Validator(gen_eth_addr(i), keypair.public_key(), i) for i, keypair in enumerate(validator_keypairs) ] -# Validators must be sorted by their public key -validators.sort(key=lambda v: v.address) - # Each validator holds their own DKG instance and generates a transcript every # validator, including themselves messages = [] @@ -52,11 +50,11 @@ def gen_eth_addr(i: int) -> str: # Server can aggregate the transcripts server_aggregate = dkg.aggregate_transcripts(messages) -assert server_aggregate.verify(shares_num, messages) +assert server_aggregate.verify(validators_num, messages) # And the client can also aggregate and verify the transcripts client_aggregate = AggregatedTranscript(messages) -assert client_aggregate.verify(shares_num, messages) +assert client_aggregate.verify(validators_num, messages) # In the meantime, the client creates a ciphertext and decryption request msg = "abc".encode() @@ -76,7 +74,7 @@ def gen_eth_addr(i: int) -> str: # We can also obtain the aggregated transcript from the side-channel (deserialize) aggregate = AggregatedTranscript(messages) - assert aggregate.verify(shares_num, messages) + assert aggregate.verify(validators_num, messages) # The ciphertext is obtained from the client diff --git a/ferveo-python/examples/server_api_simple.py b/ferveo-python/examples/server_api_simple.py index 5fd2c8e5..44fb69c4 100644 --- a/ferveo-python/examples/server_api_simple.py +++ b/ferveo-python/examples/server_api_simple.py @@ -17,7 +17,8 @@ def gen_eth_addr(i: int) -> str: tau = 1 security_threshold = 3 shares_num = 4 -validator_keypairs = [Keypair.random() for _ in range(0, shares_num)] +validators_num = shares_num + 2 +validator_keypairs = [Keypair.random() for _ in range(0, validators_num)] validators = [ Validator(gen_eth_addr(i), keypair.public_key(), i) for i, keypair in enumerate(validator_keypairs) @@ -52,11 +53,11 @@ def gen_eth_addr(i: int) -> str: # Server can aggregate the transcripts server_aggregate = dkg.aggregate_transcripts(messages) -assert server_aggregate.verify(shares_num, messages) +assert server_aggregate.verify(validators_num, messages) # And the client can also aggregate and verify the transcripts client_aggregate = AggregatedTranscript(messages) -assert client_aggregate.verify(shares_num, messages) +assert client_aggregate.verify(validators_num, messages) # In the meantime, the client creates a ciphertext and decryption request msg = "abc".encode() @@ -79,7 +80,7 @@ def gen_eth_addr(i: int) -> str: # We can also obtain the aggregated transcript from the side-channel (deserialize) aggregate = AggregatedTranscript(messages) - assert aggregate.verify(shares_num, messages) + assert aggregate.verify(validators_num, messages) # The ciphertext is obtained from the client diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py index 478628b1..fd906e54 100644 --- a/ferveo-python/ferveo/__init__.py +++ b/ferveo-python/ferveo/__init__.py @@ -30,8 +30,13 @@ InvalidDkgPublicKey, InsufficientValidators, InvalidTranscriptAggregate, - ValidatorsNotSorted, ValidatorPublicKeyMismatch, SerializationError, InvalidVariant, + InvalidDkgParameters, + InvalidDkgParametersForPrecomputedVariant, + InvalidShareIndex, + DuplicatedShareIndex, + NoTranscriptsToAggregate, + InvalidAggregateVerificationParameters, ) diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index a7c5abb6..ba7e7403 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -25,12 +25,14 @@ class FerveoPublicKey: @final class Validator: - def __init__(self, address: str, public_key: FerveoPublicKey): ... + def __init__(self, address: str, public_key: FerveoPublicKey, share_index: int): ... address: str public_key: FerveoPublicKey + share_index: int + @final class Transcript: @staticmethod @@ -104,7 +106,9 @@ class DecryptionSharePrecomputed: @final class AggregatedTranscript: def __init__(self, messages: Sequence[ValidatorMessage]): ... - def verify(self, shares_num: int, messages: Sequence[ValidatorMessage]) -> bool: ... + def verify( + self, validators_num: int, messages: Sequence[ValidatorMessage] + ) -> bool: ... def create_decryption_share_simple( self, dkg: Dkg, @@ -189,11 +193,29 @@ class InsufficientValidators(Exception): class InvalidTranscriptAggregate(Exception): pass -class ValidatorsNotSorted(Exception): - pass - class ValidatorPublicKeyMismatch(Exception): pass class SerializationError(Exception): pass + +class InvalidVariant(Exception): + pass + +class InvalidDkgParameters(Exception): + pass + +class InvalidDkgParametersForPrecomputedVariant(Exception): + pass + +class InvalidShareIndex(Exception): + pass + +class DuplicatedShareIndex(Exception): + pass + +class NoTranscriptsToAggregate(Exception): + pass + +class InvalidAggregateVerificationParameters(Exception): + pass diff --git a/ferveo-python/test/test_ferveo.py b/ferveo-python/test/test_ferveo.py index b3496d3e..51af3867 100644 --- a/ferveo-python/test/test_ferveo.py +++ b/ferveo-python/test/test_ferveo.py @@ -5,6 +5,7 @@ combine_decryption_shares_simple, combine_decryption_shares_precomputed, decrypt_with_shared_secret, + AggregatedTranscript, Keypair, Validator, ValidatorMessage, @@ -37,18 +38,29 @@ def combine_shares_for_variant(v: FerveoVariant, decryption_shares): raise ValueError("Unknown variant") -def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_to_use): +def scenario_for_variant( + variant: FerveoVariant, shares_num, validators_num, threshold, shares_to_use +): if variant not in [FerveoVariant.Simple, FerveoVariant.Precomputed]: raise ValueError("Unknown variant: " + variant) + if validators_num < shares_num: + raise ValueError("validators_num must be >= shares_num") + + if variant == FerveoVariant.Precomputed and shares_to_use != validators_num: + raise ValueError( + "In precomputed variant, shares_to_use must be equal to validators_num" + ) + tau = 1 - validator_keypairs = [Keypair.random() for _ in range(0, shares_num)] + validator_keypairs = [Keypair.random() for _ in range(0, validators_num)] validators = [ Validator(gen_eth_addr(i), keypair.public_key(), i) for i, keypair in enumerate(validator_keypairs) ] - validators.sort(key=lambda v: v.address) + # Each validator holds their own DKG instance and generates a transcript every + # validator, including themselves messages = [] for sender in validators: dkg = Dkg( @@ -60,6 +72,7 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t ) messages.append(ValidatorMessage(sender, dkg.generate_transcript())) + # Both client and server should be able to verify the aggregated transcript dkg = Dkg( tau=tau, shares_num=shares_num, @@ -67,18 +80,23 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t validators=validators, me=validators[0], ) - pvss_aggregated = dkg.aggregate_transcripts(messages) - assert pvss_aggregated.verify(shares_num, messages) + server_aggregate = dkg.aggregate_transcripts(messages) + assert server_aggregate.verify(validators_num, messages) - dkg_pk_bytes = bytes(dkg.public_key) - dkg_pk = DkgPublicKey.from_bytes(dkg_pk_bytes) + client_aggregate = AggregatedTranscript(messages) + assert client_aggregate.verify(validators_num, messages) + # Client creates a ciphertext and requests decryption shares from validators msg = "abc".encode() aad = "my-aad".encode() - ciphertext = encrypt(msg, aad, dkg_pk) + ciphertext = encrypt(msg, aad, dkg.public_key) + # Having aggregated the transcripts, the validators can now create decryption shares decryption_shares = [] for validator, validator_keypair in zip(validators, validator_keypairs): + assert validator.public_key == validator_keypair.public_key() + print("validator: ", validator.share_index) + dkg = Dkg( tau=tau, shares_num=shares_num, @@ -87,15 +105,17 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t me=validator, ) pvss_aggregated = dkg.aggregate_transcripts(messages) - assert pvss_aggregated.verify(shares_num, messages) + assert pvss_aggregated.verify(validators_num, messages) decryption_share = decryption_share_for_variant(variant, pvss_aggregated)( dkg, ciphertext.header, aad, validator_keypair ) decryption_shares.append(decryption_share) - decryption_shares = decryption_shares[:shares_to_use] + # We are limiting the number of decryption shares to use for testing purposes + # decryption_shares = decryption_shares[:shares_to_use] + # Client combines the decryption shares and decrypts the ciphertext shared_secret = combine_shares_for_variant(variant, decryption_shares) if variant == FerveoVariant.Simple and len(decryption_shares) < threshold: @@ -103,7 +123,7 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t decrypt_with_shared_secret(ciphertext, aad, shared_secret) return - if variant == FerveoVariant.Precomputed and len(decryption_shares) < shares_num: + if variant == FerveoVariant.Precomputed and len(decryption_shares) < threshold: with pytest.raises(ThresholdEncryptionError): decrypt_with_shared_secret(ciphertext, aad, shared_secret) return @@ -113,27 +133,55 @@ def scenario_for_variant(variant: FerveoVariant, shares_num, threshold, shares_t def test_simple_tdec_has_enough_messages(): - scenario_for_variant( - FerveoVariant.Simple, shares_num=4, threshold=3, shares_to_use=3 - ) + shares_num = 4 + threshold = shares_num - 1 + for validators_num in [shares_num, shares_num + 2]: + scenario_for_variant( + FerveoVariant.Simple, + shares_num=shares_num, + validators_num=validators_num, + threshold=threshold, + shares_to_use=threshold, + ) def test_simple_tdec_doesnt_have_enough_messages(): - scenario_for_variant( - FerveoVariant.Simple, shares_num=4, threshold=3, shares_to_use=2 - ) + shares_num = 4 + threshold = shares_num - 1 + for validators_num in [shares_num, shares_num + 2]: + scenario_for_variant( + FerveoVariant.Simple, + shares_num=shares_num, + validators_num=validators_num, + threshold=threshold, + shares_to_use=validators_num - 1, + ) def test_precomputed_tdec_has_enough_messages(): - scenario_for_variant( - FerveoVariant.Precomputed, shares_num=4, threshold=4, shares_to_use=4 - ) + shares_num = 4 + threshold = shares_num # in precomputed variant, we need all shares + for validators_num in [shares_num, shares_num + 2]: + scenario_for_variant( + FerveoVariant.Precomputed, + shares_num=shares_num, + validators_num=validators_num, + threshold=threshold, + shares_to_use=validators_num, + ) def test_precomputed_tdec_doesnt_have_enough_messages(): - scenario_for_variant( - FerveoVariant.Precomputed, shares_num=4, threshold=4, shares_to_use=3 - ) + shares_num = 4 + threshold = shares_num # in precomputed variant, we need all shares + for validators_num in [shares_num, shares_num + 2]: + scenario_for_variant( + FerveoVariant.Simple, + shares_num=shares_num, + validators_num=validators_num, + threshold=threshold, + shares_to_use=threshold - 1, + ) PARAMS = [ diff --git a/ferveo-wasm/examples/node/src/main.test.ts b/ferveo-wasm/examples/node/src/main.test.ts index 5814a53d..00da665b 100644 --- a/ferveo-wasm/examples/node/src/main.test.ts +++ b/ferveo-wasm/examples/node/src/main.test.ts @@ -22,11 +22,16 @@ const genEthAddr = (i: number) => { return EthereumAddress.fromString(ethAddr); }; -const tau = 1; -function setupTest(sharesNum :number, threshold: number) { +const TAU = 1; + +function setupTest( + sharesNum: number, + validatorsNum: number, + threshold: number +) { const validatorKeypairs: Keypair[] = []; const validators: Validator[] = []; - for (let i = 0; i < sharesNum; i++) { + for (let i = 0; i < validatorsNum; i++) { const keypair = Keypair.random(); validatorKeypairs.push(keypair); const validator = new Validator(genEthAddr(i), keypair.publicKey, i); @@ -37,7 +42,7 @@ function setupTest(sharesNum :number, threshold: number) { // validator, including themselves const messages: ValidatorMessage[] = []; validators.forEach((sender) => { - const dkg = new Dkg(tau, sharesNum, threshold, validators, sender); + const dkg = new Dkg(TAU, sharesNum, threshold, validators, sender); const transcript = dkg.generateTranscript(); const message = new ValidatorMessage(sender, transcript); messages.push(message); @@ -45,16 +50,16 @@ function setupTest(sharesNum :number, threshold: number) { // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts - const dkg = new Dkg(tau, sharesNum, threshold, validators, validators[0]); + const dkg = new Dkg(TAU, sharesNum, threshold, validators, validators[0]); const serverAggregate = dkg.aggregateTranscript(messages); - expect(serverAggregate.verify(sharesNum, messages)).toBe(true); + expect(serverAggregate.verify(validatorsNum, messages)).toBe(true); // Client can also aggregate the transcripts and verify them const clientAggregate = new AggregatedTranscript(messages); - expect(clientAggregate.verify(sharesNum, messages)).toBe(true); + expect(clientAggregate.verify(validatorsNum, messages)).toBe(true); - // In the meantime, the client creates a ciphertext and decryption request + // Client creates a ciphertext and requests decryption shares from validators const msg = Buffer.from("my-msg"); const aad = Buffer.from("my-aad"); const ciphertext = ferveoEncrypt(msg, aad, dkg.publicKey()); @@ -73,94 +78,78 @@ function setupTest(sharesNum :number, threshold: number) { // This test suite replicates tests from ferveo-wasm/tests/node.rs describe("ferveo-wasm", () => { it("simple tdec variant", () => { - const sharesNum = 4; - const threshold = 3; - const { - validatorKeypairs, - validators, - messages, - msg, - aad, - ciphertext, - } = setupTest(sharesNum, threshold); - - // Having aggregated the transcripts, the validators can now create decryption shares - const decryptionShares: DecryptionShareSimple[] = []; - zip(validators, validatorKeypairs).forEach(([validator, keypair]) => { - expect(validator.publicKey.equals(keypair.publicKey)).toBe(true); - - const dkg = new Dkg(tau, sharesNum, threshold, validators, validator); - const aggregate = dkg.aggregateTranscript(messages); - const isValid = aggregate.verify(sharesNum, messages); - expect(isValid).toBe(true); - - const decryptionShare = aggregate.createDecryptionShareSimple( - dkg, - ciphertext.header, - aad, - keypair - ); - decryptionShares.push(decryptionShare); + const sharesNum = 4; + const threshold = sharesNum - 1; + [sharesNum, sharesNum + 2].forEach((validatorsNum) => { + const { validatorKeypairs, validators, messages, msg, aad, ciphertext } = + setupTest(sharesNum, validatorsNum, threshold); + + // Having aggregated the transcripts, the validators can now create decryption shares + const decryptionShares: DecryptionShareSimple[] = []; + zip(validators, validatorKeypairs).forEach(([validator, keypair]) => { + expect(validator.publicKey.equals(keypair.publicKey)).toBe(true); + + const dkg = new Dkg(TAU, sharesNum, threshold, validators, validator); + const aggregate = dkg.aggregateTranscript(messages); + const isValid = aggregate.verify(validatorsNum, messages); + expect(isValid).toBe(true); + + const decryptionShare = aggregate.createDecryptionShareSimple( + dkg, + ciphertext.header, + aad, + keypair + ); + decryptionShares.push(decryptionShare); + }); + + // Now, the decryption share can be used to decrypt the ciphertext + // This part is in the client API + + const sharedSecret = combineDecryptionSharesSimple(decryptionShares); + + // The client should have access to the public parameters of the DKG + + const plaintext = decryptWithSharedSecret(ciphertext, aad, sharedSecret); + expect(Buffer.from(plaintext)).toEqual(msg); }); - - // Now, the decryption share can be used to decrypt the ciphertext - // This part is in the client API - - const sharedSecret = combineDecryptionSharesSimple( - decryptionShares, - ); - - // The client should have access to the public parameters of the DKG - - const plaintext = decryptWithSharedSecret( - ciphertext, - aad, - sharedSecret, - ); - expect(Buffer.from(plaintext)).toEqual(msg); }); it("precomputed tdec variant", () => { - const sharesNum = 4; - const threshold = sharesNum; // threshold is equal to sharesNum in precomputed variant - const { - validatorKeypairs, - validators, - messages, - msg, - aad, - ciphertext, - } = setupTest(sharesNum, threshold); - - // Having aggregated the transcripts, the validators can now create decryption shares - const decryptionShares: DecryptionSharePrecomputed[] = []; - zip(validators, validatorKeypairs).forEach(([validator, keypair]) => { - const dkg = new Dkg(tau, sharesNum, threshold, validators, validator); - const aggregate = dkg.aggregateTranscript(messages); - const isValid = aggregate.verify(sharesNum, messages); - expect(isValid).toBe(true); - - const decryptionShare = aggregate.createDecryptionSharePrecomputed( - dkg, - ciphertext.header, - aad, - keypair - ); - decryptionShares.push(decryptionShare); + const sharesNum = 4; + const threshold = sharesNum; // threshold is equal to sharesNum in precomputed variant + [sharesNum, sharesNum + 2].forEach((validatorsNum) => { + const { validatorKeypairs, validators, messages, msg, aad, ciphertext } = + setupTest(sharesNum, validatorsNum, threshold); + + // Having aggregated the transcripts, the validators can now create decryption shares + const decryptionShares: DecryptionSharePrecomputed[] = []; + zip(validators, validatorKeypairs).forEach(([validator, keypair]) => { + expect(validator.publicKey.equals(keypair.publicKey)).toBe(true); + + const dkg = new Dkg(TAU, sharesNum, threshold, validators, validator); + const aggregate = dkg.aggregateTranscript(messages); + const isValid = aggregate.verify(validatorsNum, messages); + expect(isValid).toBe(true); + + const decryptionShare = aggregate.createDecryptionSharePrecomputed( + dkg, + ciphertext.header, + aad, + keypair + ); + decryptionShares.push(decryptionShare); + }); + + // Now, the decryption share can be used to decrypt the ciphertext + // This part is in the client API + + const sharedSecret = combineDecryptionSharesPrecomputed(decryptionShares); + + // The client should have access to the public parameters of the DKG + + const plaintext = decryptWithSharedSecret(ciphertext, aad, sharedSecret); + expect(Buffer.from(plaintext)).toEqual(msg); }); - - // Now, the decryption share can be used to decrypt the ciphertext - // This part is in the client API - - const sharedSecret = combineDecryptionSharesPrecomputed(decryptionShares); - - // The client should have access to the public parameters of the DKG - - const plaintext = decryptWithSharedSecret( - ciphertext, - aad, - sharedSecret, - ); - expect(Buffer.from(plaintext)).toEqual(msg); }); }); diff --git a/ferveo-wasm/tests/node.rs b/ferveo-wasm/tests/node.rs index bae5750b..7b35efa5 100644 --- a/ferveo-wasm/tests/node.rs +++ b/ferveo-wasm/tests/node.rs @@ -18,8 +18,12 @@ type TestSetup = ( const TAU: u32 = 0; -fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup { - let validator_keypairs = (0..shares_num as usize) +fn setup_dkg( + shares_num: u32, + validators_num: u32, + security_threshold: u32, +) -> TestSetup { + let validator_keypairs = (0..validators_num as usize) .map(gen_keypair) .collect::>(); let validators = validator_keypairs @@ -32,7 +36,7 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup { // Each validator holds their own DKG instance and generates a transcript every // validator, including themselves let messages = validators.iter().map(|sender| { - let mut dkg = Dkg::new( + let mut validator_dkg = Dkg::new( TAU, shares_num, security_threshold, @@ -40,7 +44,7 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup { sender, ) .unwrap(); - let transcript = dkg.generate_transcript().unwrap(); + let transcript = validator_dkg.generate_transcript().unwrap(); ValidatorMessage::new(sender, &transcript).unwrap() }); @@ -61,12 +65,16 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup { // Server can aggregate the transcripts and verify them let server_aggregate = dkg.aggregate_transcripts(&messages_js).unwrap(); - let is_valid = server_aggregate.verify(shares_num, &messages_js).unwrap(); + let is_valid = server_aggregate + .verify(validators_num, &messages_js) + .unwrap(); assert!(is_valid); // Client can also aggregate the transcripts and verify them let client_aggregate = AggregatedTranscript::new(&messages_js).unwrap(); - let is_valid = client_aggregate.verify(shares_num, &messages_js).unwrap(); + let is_valid = client_aggregate + .verify(validators_num, &messages_js) + .unwrap(); assert!(is_valid); // In the meantime, the client creates a ciphertext and decryption request @@ -88,105 +96,116 @@ fn setup_dkg(shares_num: u32, security_threshold: u32) -> TestSetup { #[wasm_bindgen_test] fn tdec_simple() { let shares_num = 16; - let security_threshold = 10; - let ( - validator_keypairs, - validators, - validators_js, - messages_js, - msg, - aad, - ciphertext, - ) = setup_dkg(shares_num, security_threshold); - - // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares = zip_eq(validators, validator_keypairs) - .map(|(validator, keypair)| { - let mut dkg = Dkg::new( - TAU, - shares_num, - security_threshold, - &validators_js, - &validator, - ) - .unwrap(); - let aggregate = dkg.aggregate_transcripts(&messages_js).unwrap(); - let is_valid = aggregate.verify(shares_num, &messages_js).unwrap(); - assert!(is_valid); - - aggregate - .create_decryption_share_simple( - &dkg, - &ciphertext.header().unwrap(), - &aad, - &keypair, + let security_threshold = shares_num / 2; + for validators_num in [shares_num, shares_num + 2] { + let ( + validator_keypairs, + validators, + validators_js, + messages_js, + msg, + aad, + ciphertext, + ) = setup_dkg(shares_num, validators_num, security_threshold); + + // Having aggregated the transcripts, the validators can now create decryption shares + let decryption_shares = zip_eq(validators, validator_keypairs) + .map(|(validator, keypair)| { + let mut dkg = Dkg::new( + TAU, + shares_num, + security_threshold, + &validators_js, + &validator, ) - .unwrap() - }) - .collect::>(); - let decryption_shares_js = into_js_array(decryption_shares); - - // Now, the decryption share can be used to decrypt the ciphertext - // This part is in the client API - - let shared_secret = - combine_decryption_shares_simple(&decryption_shares_js).unwrap(); - - // The client should have access to the public parameters of the DKG - let plaintext = - decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret).unwrap(); - assert_eq!(msg, plaintext); + .unwrap(); + let aggregate = + dkg.aggregate_transcripts(&messages_js).unwrap(); + let is_valid = + aggregate.verify(validators_num, &messages_js).unwrap(); + assert!(is_valid); + + aggregate + .create_decryption_share_simple( + &dkg, + &ciphertext.header().unwrap(), + &aad, + &keypair, + ) + .unwrap() + }) + .collect::>(); + let decryption_shares_js = into_js_array(decryption_shares); + + // Now, the decryption share can be used to decrypt the ciphertext + // This part is in the client API + + let shared_secret = + combine_decryption_shares_simple(&decryption_shares_js).unwrap(); + + // The client should have access to the public parameters of the DKG + let plaintext = + decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret) + .unwrap(); + assert_eq!(msg, plaintext); + } } #[wasm_bindgen_test] fn tdec_precomputed() { let shares_num = 16; let security_threshold = shares_num; // Must be equal to shares_num in precomputed variant - let ( - validator_keypairs, - validators, - validators_js, - messages_js, - msg, - aad, - ciphertext, - ) = setup_dkg(shares_num, security_threshold); - - // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares = zip_eq(validators, validator_keypairs) - .map(|(validator, keypair)| { - let mut dkg = Dkg::new( - TAU, - shares_num, - security_threshold, - &validators_js, - &validator, - ) - .unwrap(); - let aggregate = dkg.aggregate_transcripts(&messages_js).unwrap(); - let is_valid = aggregate.verify(shares_num, &messages_js).unwrap(); - assert!(is_valid); - - aggregate - .create_decryption_share_precomputed( - &dkg, - &ciphertext.header().unwrap(), - &aad, - &keypair, + for validators_num in [shares_num, shares_num + 2] { + let ( + validator_keypairs, + validators, + validators_js, + messages_js, + msg, + aad, + ciphertext, + ) = setup_dkg(shares_num, validators_num, security_threshold); + + // Having aggregated the transcripts, the validators can now create decryption shares + let decryption_shares = zip_eq(validators, validator_keypairs) + .map(|(validator, keypair)| { + let mut dkg = Dkg::new( + TAU, + shares_num, + security_threshold, + &validators_js, + &validator, ) - .unwrap() - }) - .collect::>(); - let decryption_shares_js = into_js_array(decryption_shares); - - // Now, the decryption share can be used to decrypt the ciphertext - // This part is in the client API - - let shared_secret = - combine_decryption_shares_precomputed(&decryption_shares_js).unwrap(); - - // The client should have access to the public parameters of the DKG - let plaintext = - decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret).unwrap(); - assert_eq!(msg, plaintext); + .unwrap(); + let aggregate = + dkg.aggregate_transcripts(&messages_js).unwrap(); + let is_valid = + aggregate.verify(validators_num, &messages_js).unwrap(); + assert!(is_valid); + + aggregate + .create_decryption_share_precomputed( + &dkg, + &ciphertext.header().unwrap(), + &aad, + &keypair, + ) + .unwrap() + }) + .collect::>(); + let decryption_shares_js = into_js_array(decryption_shares); + + // Now, the decryption share can be used to decrypt the ciphertext + // This part is in the client API + + let shared_secret = + combine_decryption_shares_precomputed(&decryption_shares_js) + .unwrap(); + + // The client should have access to the public parameters of the DKG + let plaintext = + decrypt_with_shared_secret(&ciphertext, &aad, &shared_secret) + .unwrap(); + assert_eq!(msg, plaintext); + } } diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 4c6defd2..7328515e 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -272,7 +272,9 @@ fn make_pvss_map(messages: &[ValidatorMessage]) -> PVSSMap { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct AggregatedTranscript(PubliclyVerifiableSS); +pub struct AggregatedTranscript( + pub(crate) PubliclyVerifiableSS, +); impl AggregatedTranscript { pub fn new(messages: &[ValidatorMessage]) -> Result { @@ -286,12 +288,20 @@ impl AggregatedTranscript { pub fn verify( &self, - shares_num: u32, + validators_num: u32, messages: &[ValidatorMessage], ) -> Result { + if validators_num < messages.len() as u32 { + return Err(Error::InvalidAggregateVerificationParameters( + validators_num, + messages.len() as u32, + )); + } + let pvss_params = PubliclyVerifiableParams::::default(); - let domain = GeneralEvaluationDomain::::new(shares_num as usize) - .expect("Unable to construct an evaluation domain"); + let domain = + GeneralEvaluationDomain::::new(validators_num as usize) + .expect("Unable to construct an evaluation domain"); let is_valid_optimistic = self.0.verify_optimistic(); if !is_valid_optimistic { @@ -324,6 +334,7 @@ impl AggregatedTranscript { aad: &[u8], validator_keypair: &Keypair, ) -> Result { + // Prevent users from using the precomputed variant with improper DKG parameters if dkg.0.dkg_params.shares_num() != dkg.0.dkg_params.security_threshold() { @@ -332,18 +343,12 @@ impl AggregatedTranscript { dkg.0.dkg_params.security_threshold(), )); } - let domain_points: Vec<_> = dkg - .0 - .domain - .elements() - .take(dkg.0.dkg_params.shares_num() as usize) - .collect(); self.0.make_decryption_share_simple_precomputed( &ciphertext_header.0, aad, &validator_keypair.decryption_key, dkg.0.me.share_index as usize, - &domain_points, + &dkg.0.domain_points(), &dkg.0.pvss_params.g_inv(), ) } @@ -362,7 +367,7 @@ impl AggregatedTranscript { dkg.0.me.share_index as usize, &dkg.0.pvss_params.g_inv(), )?; - let domain_point = dkg.0.domain.element(dkg.0.me.share_index as usize); + let domain_point = dkg.0.get_domain_point(dkg.0.me.share_index)?; Ok(DecryptionShareSimple { share, domain_point, @@ -433,8 +438,9 @@ mod test_ferveo_api { tau: u32, security_threshold: u32, shares_num: u32, + validators_num: u32, ) -> TestInputs { - let validator_keypairs = gen_keypairs(shares_num); + let validator_keypairs = gen_keypairs(validators_num); let validators = validator_keypairs .iter() .enumerate() @@ -474,16 +480,22 @@ mod test_ferveo_api { assert_eq!(dkg_pk, deserialized); } - #[test_case(4; "number of shares (validators) is a power of 2")] - #[test_case(7; "number of shares (validators) is not a power of 2")] - fn test_server_api_tdec_precomputed(shares_num: u32) { + #[test_case(4, 4; "number of shares (validators) is a power of 2")] + #[test_case(7, 7; "number of shares (validators) is not a power of 2")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn test_server_api_tdec_precomputed(shares_num: u32, validators_num: u32) { let rng = &mut StdRng::seed_from_u64(0); // In precomputed variant, the security threshold is equal to the number of shares let security_threshold = shares_num; - let (messages, validators, validator_keypairs) = - make_test_inputs(rng, TAU, security_threshold, shares_num); + let (messages, validators, validator_keypairs) = make_test_inputs( + rng, + TAU, + security_threshold, + shares_num, + validators_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts @@ -493,7 +505,7 @@ mod test_ferveo_api { .unwrap(); let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); + assert!(pvss_aggregated.verify(validators_num, &messages).unwrap()); // At this point, any given validator should be able to provide a DKG public key let dkg_public_key = dkg.public_key(); @@ -516,7 +528,9 @@ mod test_ferveo_api { ) .unwrap(); let aggregate = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); + assert!(pvss_aggregated + .verify(validators_num, &messages) + .unwrap()); // And then each validator creates their own decryption share aggregate @@ -556,15 +570,21 @@ mod test_ferveo_api { assert!(result.is_err()); } - #[test_case(4; "number of shares (validators) is a power of 2")] - #[test_case(7; "number of shares (validators) is not a power of 2")] - fn test_server_api_tdec_simple(shares_num: u32) { + #[test_case(4, 4; "number of shares (validators) is a power of 2")] + #[test_case(7, 7; "number of shares (validators) is not a power of 2")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn test_server_api_tdec_simple(shares_num: u32, validators_num: u32) { let rng = &mut StdRng::seed_from_u64(0); let security_threshold = shares_num / 2 + 1; - let (messages, validators, validator_keypairs) = - make_test_inputs(rng, TAU, security_threshold, shares_num); + let (messages, validators, validator_keypairs) = make_test_inputs( + rng, + TAU, + security_threshold, + shares_num, + validators_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts @@ -578,7 +598,7 @@ mod test_ferveo_api { .unwrap(); let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); + assert!(pvss_aggregated.verify(validators_num, &messages).unwrap()); // At this point, any given validator should be able to provide a DKG public key let public_key = dkg.public_key(); @@ -600,7 +620,7 @@ mod test_ferveo_api { ) .unwrap(); let aggregate = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(aggregate.verify(shares_num, &messages).unwrap()); + assert!(aggregate.verify(validators_num, &messages).unwrap()); aggregate .create_decryption_share_simple( &dkg, @@ -640,104 +660,152 @@ mod test_ferveo_api { // implementation for aggregation and aggregate verification. // Here, we focus on testing user-facing APIs for server and client users. - #[test] - fn server_side_local_verification() { + #[test_case(4, 4; "number of shares (validators) is a power of 2")] + #[test_case(7, 7; "number of shares (validators) is not a power of 2")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn server_side_local_verification(shares_num: u32, validators_num: u32) { let rng = &mut StdRng::seed_from_u64(0); + let security_threshold = shares_num / 2 + 1; - let (messages, validators, _) = - make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM); + let (messages, validators, _) = make_test_inputs( + rng, + TAU, + security_threshold, + shares_num, + validators_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts let me = validators[0].clone(); let mut dkg = - Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me) + Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); - let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(local_aggregate - .verify(dkg.0.dkg_params.shares_num(), &messages) - .is_ok()); + let good_aggregate = dkg.aggregate_transcripts(&messages).unwrap(); + assert!(good_aggregate.verify(validators_num, &messages).is_ok()); // Test negative cases // Notice that the dkg instance is mutable, so we need to get a fresh one // for every test case + // Should fail if the number of validators is less than the number of messages + assert!(matches!( + good_aggregate.verify(messages.len() as u32 - 1, &messages), + Err(Error::InvalidAggregateVerificationParameters(_, _)) + )); + // Should fail if no transcripts are provided let mut dkg = - Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me) + Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); - let result = dkg.aggregate_transcripts(&[]); - assert!(result.is_err()); + assert!(matches!( + dkg.aggregate_transcripts(&[]), + Err(Error::NoTranscriptsToAggregate) + )); // Not enough transcripts let mut dkg = - Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me) + Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); - let not_enough_messages = &messages[..SECURITY_THRESHOLD as usize - 1]; - assert!(not_enough_messages.len() < SECURITY_THRESHOLD as usize); + let not_enough_messages = &messages[..security_threshold as usize - 1]; + assert!(not_enough_messages.len() < security_threshold as usize); let insufficient_aggregate = dkg.aggregate_transcripts(not_enough_messages).unwrap(); - let result = insufficient_aggregate.verify(SHARES_NUM, &messages); - assert!(result.is_err()); + assert!(matches!( + insufficient_aggregate.verify(validators_num, &messages), + Err(Error::InvalidTranscriptAggregate) + )); // Unexpected transcripts in the aggregate or transcripts from a different ritual // Using same DKG parameters, but different DKG instances and validators let mut dkg = - Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me) + Dkg::new(TAU, shares_num, security_threshold, &validators, &me) .unwrap(); - let (bad_messages, _, _) = - make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM); + let (bad_messages, _, _) = make_test_inputs( + rng, + TAU, + security_threshold, + shares_num, + validators_num, + ); let mixed_messages = [&messages[..2], &bad_messages[..1]].concat(); let bad_aggregate = dkg.aggregate_transcripts(&mixed_messages).unwrap(); - let result = bad_aggregate.verify(SHARES_NUM, &messages); - assert!(result.is_err()); + assert!(matches!( + bad_aggregate.verify(validators_num, &messages), + Err(Error::InvalidTranscriptAggregate) + )); } - #[test] - fn client_side_local_verification() { + #[test_case(4, 4; "number of shares (validators) is a power of 2")] + #[test_case(7, 7; "number of shares (validators) is not a power of 2")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn client_side_local_verification(shares_num: u32, validators_num: u32) { let rng = &mut StdRng::seed_from_u64(0); + let security_threshold = shares_num / 2 + 1; - let (messages, _, _) = - make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM); + let (messages, _, _) = make_test_inputs( + rng, + TAU, + security_threshold, + shares_num, + validators_num, + ); // We only need `security_threshold` transcripts to aggregate - let messages = &messages[..SECURITY_THRESHOLD as usize]; + let messages = &messages[..security_threshold as usize]; // Create an aggregated transcript on the client side - let aggregated_transcript = - AggregatedTranscript::new(messages).unwrap(); + let good_aggregate = AggregatedTranscript::new(messages).unwrap(); // We are separating the verification from the aggregation since the client may fetch // the aggregate from a side-channel or decide to persist it and verify it later // Now, the client can verify the aggregated transcript - let result = aggregated_transcript.verify(SHARES_NUM, messages); + let result = good_aggregate.verify(validators_num, messages); assert!(result.is_ok()); assert!(result.unwrap()); // Test negative cases + // Should fail if the number of validators is less than the number of messages + assert!(matches!( + good_aggregate.verify(messages.len() as u32 - 1, messages), + Err(Error::InvalidAggregateVerificationParameters(_, _)) + )); + // Should fail if no transcripts are provided - let result = AggregatedTranscript::new(&[]); - assert!(result.is_err()); + assert!(matches!( + AggregatedTranscript::new(&[]), + Err(Error::NoTranscriptsToAggregate) + )); // Not enough transcripts - let not_enough_messages = &messages[..SECURITY_THRESHOLD as usize - 1]; - assert!(not_enough_messages.len() < SECURITY_THRESHOLD as usize); + let not_enough_messages = &messages[..security_threshold as usize - 1]; + assert!(not_enough_messages.len() < security_threshold as usize); let insufficient_aggregate = AggregatedTranscript::new(not_enough_messages).unwrap(); - let result = insufficient_aggregate.verify(SHARES_NUM, messages); - assert!(result.is_err()); + let _result = insufficient_aggregate.verify(validators_num, messages); + assert!(matches!( + insufficient_aggregate.verify(validators_num, messages), + Err(Error::InvalidTranscriptAggregate) + )); // Unexpected transcripts in the aggregate or transcripts from a different ritual // Using same DKG parameters, but different DKG instances and validators - let (bad_messages, _, _) = - make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM); + let (bad_messages, _, _) = make_test_inputs( + rng, + TAU, + security_threshold, + shares_num, + validators_num, + ); let mixed_messages = [&messages[..2], &bad_messages[..1]].concat(); let bad_aggregate = AggregatedTranscript::new(&mixed_messages).unwrap(); - let result = bad_aggregate.verify(SHARES_NUM, messages); - assert!(result.is_err()); + assert!(matches!( + bad_aggregate.verify(validators_num, messages), + Err(Error::InvalidTranscriptAggregate) + )); } } diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index fab67561..fe534af8 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -92,30 +92,35 @@ impl From for PyErr { } Error::InvalidVariant(variant) => { InvalidVariant::new_err(variant.to_string()) - }, - Error::InvalidDkgParameters(num_shares, security_threshold) => { + } + Error::InvalidDkgParameters(shares_num, security_threshold) => { InvalidDkgParameters::new_err(format!( - "num_shares: {num_shares}, security_threshold: {security_threshold}" + "shares_num: {shares_num}, security_threshold: {security_threshold}" )) - }, + } Error::InvalidShareIndex(index) => { InvalidShareIndex::new_err(format!( "{index}" )) - }, - Error::InvalidDkgParametersForPrecomputedVariant(num_shares, security_threshold) => { - InvalidDkgParameters::new_err(format!( - "num_shares: {num_shares}, security_threshold: {security_threshold}" + } + Error::InvalidDkgParametersForPrecomputedVariant(shares_num, security_threshold) => { + InvalidDkgParametersForPrecomputedVariant::new_err(format!( + "shares_num: {shares_num}, security_threshold: {security_threshold}" )) - }, + } Error::DuplicatedShareIndex(index) => { - InvalidShareIndex::new_err(format!( + DuplicatedShareIndex::new_err(format!( "{index}" )) - }, + } Error::NoTranscriptsToAggregate => { NoTranscriptsToAggregate::new_err("") - }, + } + Error::InvalidAggregateVerificationParameters(validators_num, messages_num) => { + InvalidAggregateVerificationParameters::new_err(format!( + "validators_num: {validators_num}, messages_num: {messages_num}" + )) + } }, _ => default(), } @@ -145,14 +150,24 @@ create_exception!(exceptions, InsufficientTranscriptsForAggregate, PyException); create_exception!(exceptions, InvalidDkgPublicKey, PyValueError); create_exception!(exceptions, InsufficientValidators, PyValueError); create_exception!(exceptions, InvalidTranscriptAggregate, PyValueError); -create_exception!(exceptions, ValidatorsNotSorted, PyValueError); create_exception!(exceptions, ValidatorPublicKeyMismatch, PyValueError); create_exception!(exceptions, SerializationError, PyValueError); create_exception!(exceptions, InvalidByteLength, PyValueError); create_exception!(exceptions, InvalidVariant, PyValueError); create_exception!(exceptions, InvalidDkgParameters, PyValueError); +create_exception!( + exceptions, + InvalidDkgParametersForPrecomputedVariant, + PyValueError +); create_exception!(exceptions, InvalidShareIndex, PyValueError); +create_exception!(exceptions, DuplicatedShareIndex, PyValueError); create_exception!(exceptions, NoTranscriptsToAggregate, PyValueError); +create_exception!( + exceptions, + InvalidAggregateVerificationParameters, + PyValueError +); fn from_py_bytes(bytes: &[u8]) -> PyResult { T::from_bytes(bytes) @@ -420,6 +435,11 @@ impl Validator { pub fn public_key(&self) -> FerveoPublicKey { FerveoPublicKey(self.0.public_key) } + + #[getter] + pub fn share_index(&self) -> u32 { + self.0.share_index + } } #[pyclass(module = "ferveo")] @@ -594,14 +614,14 @@ impl AggregatedTranscript { pub fn verify( &self, - shares_num: u32, + validators_num: u32, messages: Vec, ) -> PyResult { let messages: Vec<_> = messages.into_iter().map(|vm| vm.to_inner()).collect(); let is_valid = self .0 - .verify(shares_num, &messages) + .verify(validators_num, &messages) .map_err(FerveoPythonError::FerveoError)?; Ok(is_valid) } @@ -735,13 +755,33 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { "InvalidTranscriptAggregate", py.get_type::(), )?; - m.add("ValidatorsNotSorted", py.get_type::())?; m.add( "ValidatorPublicKeyMismatch", py.get_type::(), )?; m.add("SerializationError", py.get_type::())?; m.add("InvalidVariant", py.get_type::())?; + m.add( + "InvalidDkgParameters", + py.get_type::(), + )?; + m.add( + "InvalidDkgParametersForPrecomputedVariant", + py.get_type::(), + )?; + m.add("InvalidShareIndex", py.get_type::())?; + m.add( + "DuplicatedShareIndex", + py.get_type::(), + )?; + m.add( + "NoTranscriptsToAggregate", + py.get_type::(), + )?; + m.add( + "InvalidAggregateVerificationParameters", + py.get_type::(), + )?; Ok(()) } @@ -750,6 +790,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { #[cfg(test)] mod test_ferveo_python { use itertools::izip; + use test_case::test_case; use crate::{bindings_python::*, test_common::*}; @@ -759,8 +800,9 @@ mod test_ferveo_python { tau: u32, security_threshold: u32, shares_num: u32, + validators_num: u32, ) -> TestInputs { - let validator_keypairs = (0..shares_num) + let validator_keypairs = (0..validators_num) .map(|_| Keypair::random()) .collect::>(); let validators: Vec<_> = validator_keypairs @@ -799,13 +841,18 @@ mod test_ferveo_python { (messages, validators, validator_keypairs) } - #[test] - fn test_server_api_tdec_precomputed() { + #[test_case(4, 4; "number of validators equal to the number of shares")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn test_server_api_tdec_precomputed(shares_num: u32, validators_num: u32) { // In precomputed variant, the security threshold is equal to the number of shares - let security_threshold = SHARES_NUM; + let security_threshold = shares_num; - let (messages, validators, validator_keypairs) = - make_test_inputs(TAU, security_threshold, SHARES_NUM); + let (messages, validators, validator_keypairs) = make_test_inputs( + TAU, + security_threshold, + shares_num, + validators_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts @@ -813,19 +860,19 @@ mod test_ferveo_python { let me = validators[0].clone(); let mut dkg = Dkg::new( TAU, - SHARES_NUM, + shares_num, security_threshold, validators.clone(), &me, ) .unwrap(); - // Lets say that we've only receives `security_threshold` transcripts + // Lets say that we've only received `security_threshold` transcripts let messages = messages[..security_threshold as usize].to_vec(); let pvss_aggregated = dkg.aggregate_transcripts(messages.clone()).unwrap(); assert!(pvss_aggregated - .verify(SHARES_NUM, messages.clone()) + .verify(validators_num, messages.clone()) .unwrap()); // At this point, any given validator should be able to provide a DKG public key @@ -835,36 +882,37 @@ mod test_ferveo_python { let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs) - .map(|(validator, validator_keypair)| { - // Each validator holds their own instance of DKG and creates their own aggregate - let mut dkg = Dkg::new( - TAU, - SHARES_NUM, - security_threshold, - validators.clone(), - validator, - ) - .unwrap(); - let aggregate = - dkg.aggregate_transcripts(messages.clone()).unwrap(); - assert!(pvss_aggregated - .verify(SHARES_NUM, messages.clone()) - .is_ok()); - aggregate - .create_decryption_share_precomputed( - &dkg, - &ciphertext.header().unwrap(), - AAD, - validator_keypair, + let decryption_shares: Vec<_> = + izip!(validators.clone(), &validator_keypairs) + .map(|(validator, validator_keypair)| { + // Each validator holds their own instance of DKG and creates their own aggregate + let mut validator_dkg = Dkg::new( + TAU, + shares_num, + security_threshold, + validators.clone(), + &validator, ) - .unwrap() - }) - .collect(); + .unwrap(); + let aggregate = validator_dkg + .aggregate_transcripts(messages.clone()) + .unwrap(); + assert!(pvss_aggregated + .verify(validators_num, messages.clone()) + .is_ok()); + aggregate + .create_decryption_share_precomputed( + &validator_dkg, + &ciphertext.header().unwrap(), + AAD, + validator_keypair, + ) + .unwrap() + }) + .collect(); // Now, the decryption share can be used to decrypt the ciphertext // This part is part of the client API - let shared_secret = combine_decryption_shares_precomputed(decryption_shares); @@ -874,29 +922,35 @@ mod test_ferveo_python { assert_eq!(plaintext, MSG); } - #[test] - fn test_server_api_tdec_simple() { - let (messages, validators, validator_keypairs) = - make_test_inputs(TAU, SECURITY_THRESHOLD, SHARES_NUM); + #[test_case(4, 4; "number of validators equal to the number of shares")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn test_server_api_tdec_simple(shares_num: u32, validators_num: u32) { + let security_threshold = shares_num - 1; + let (messages, validators, validator_keypairs) = make_test_inputs( + TAU, + security_threshold, + shares_num, + validators_num, + ); // Now that every validator holds a dkg instance and a transcript for every other validator, // every validator can aggregate the transcripts let me = validators[0].clone(); let mut dkg = Dkg::new( TAU, - SHARES_NUM, - SECURITY_THRESHOLD, + shares_num, + security_threshold, validators.clone(), &me, ) .unwrap(); // Lets say that we've only receives `security_threshold` transcripts - let messages = messages[..SECURITY_THRESHOLD as usize].to_vec(); + let messages = messages[..security_threshold as usize].to_vec(); let pvss_aggregated = dkg.aggregate_transcripts(messages.clone()).unwrap(); assert!(pvss_aggregated - .verify(SHARES_NUM, messages.clone()) + .verify(validators_num, messages.clone()) .unwrap()); // At this point, any given validator should be able to provide a DKG public key @@ -906,32 +960,35 @@ mod test_ferveo_python { let ciphertext = encrypt(MSG.to_vec(), AAD, &dkg_public_key).unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs) - .map(|(validator, validator_keypair)| { - // Each validator holds their own instance of DKG and creates their own aggregate - let mut dkg = Dkg::new( - TAU, - SHARES_NUM, - SECURITY_THRESHOLD, - validators.clone(), - validator, - ) - .unwrap(); - let aggregate = - dkg.aggregate_transcripts(messages.clone()).unwrap(); - assert!(aggregate - .verify(SHARES_NUM, messages.clone()) - .unwrap()); - aggregate - .create_decryption_share_simple( - &dkg, - &ciphertext.header().unwrap(), - AAD, - validator_keypair, + let decryption_shares: Vec<_> = + izip!(validators.clone(), &validator_keypairs) + .map(|(validator, validator_keypair)| { + // Each validator holds their own instance of DKG and creates their own aggregate + let mut validator_dkg = Dkg::new( + TAU, + shares_num, + security_threshold, + validators.clone(), + &validator, ) - .unwrap() - }) - .collect(); + .unwrap(); + let aggregate = validator_dkg + .aggregate_transcripts(messages.clone()) + .unwrap(); + + assert!(aggregate + .verify(validators_num, messages.clone()) + .unwrap()); + aggregate + .create_decryption_share_simple( + &validator_dkg, + &ciphertext.header().unwrap(), + AAD, + validator_keypair, + ) + .unwrap() + }) + .collect(); // Now, the decryption share can be used to decrypt the ciphertext // This part is part of the client API diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs index 1396de13..07e22e3f 100644 --- a/ferveo/src/bindings_wasm.rs +++ b/ferveo/src/bindings_wasm.rs @@ -515,13 +515,15 @@ impl AggregatedTranscript { #[wasm_bindgen] pub fn verify( &self, - shares_num: u32, + validators_num: u32, messages: &ValidatorMessageArray, ) -> JsResult { set_panic_hook(); let messages = unwrap_messages_js(messages)?; - let is_valid = - self.0.verify(shares_num, &messages).map_err(map_js_err)?; + let is_valid = self + .0 + .verify(validators_num, &messages) + .map_err(map_js_err)?; Ok(is_valid) } diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 3bba9b47..e8afbe30 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -96,7 +96,7 @@ pub struct PubliclyVerifiableDkg { pub vss: PVSSMap, pub domain: ark_poly::GeneralEvaluationDomain, pub me: Validator, - pub state: DkgState, + state: DkgState, } impl PubliclyVerifiableDkg { @@ -112,7 +112,7 @@ impl PubliclyVerifiableDkg { me: &Validator, ) -> Result { let domain = ark_poly::GeneralEvaluationDomain::::new( - dkg_params.shares_num as usize, + validators.len(), ) .expect("unable to construct domain"); @@ -120,10 +120,7 @@ impl PubliclyVerifiableDkg { let validators: ValidatorsMap = validators .iter() - .enumerate() - .map(|(_validator_index, validator)| { - (validator.address.clone(), validator.clone()) - }) + .map(|validator| (validator.address.clone(), validator.clone())) .collect(); // Make sure that `me` is a known validator @@ -197,6 +194,20 @@ impl PubliclyVerifiableDkg { .into_affine() } + /// Return a domain point for the share_index + pub fn get_domain_point(&self, share_index: u32) -> Result { + let domain_points = self.domain_points(); + domain_points + .get(share_index as usize) + .ok_or_else(|| Error::InvalidShareIndex(share_index)) + .copied() + } + + /// Return an appropriate amount of domain points for the DKG + pub fn domain_points(&self) -> Vec { + self.domain.elements().take(self.validators.len()).collect() + } + /// `payload` is the content of the message pub fn verify_message( &self, @@ -323,6 +334,8 @@ pub struct Aggregation { public_key: E::G1Affine, } +// TODO: Remove these? +// TODO: These messages are not actually used anywhere, we use our own ValidatorMessage for Deal, and Aggregate for Message.Aggregate #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(bound( serialize = "AggregatedPvss: Serialize, PubliclyVerifiableSS: Serialize", @@ -370,7 +383,37 @@ mod test_dkg_init { mod test_dealing { use ark_ec::AffineRepr; - use crate::{test_common::*, DkgState, DkgState::Dealt, Validator}; + use crate::{ + test_common::*, DkgParams, DkgState, DkgState::Dealt, Error, + PubliclyVerifiableDkg, Validator, + }; + + /// Check that the canonical share indices of validators are expected and enforced + /// by the DKG methods. + #[test] + fn test_canonical_share_indices_are_enforced() { + let shares_num = 4; + let security_threshold = shares_num - 1; + let keypairs = gen_keypairs(shares_num); + let mut validators = gen_validators(&keypairs); + let me = validators[0].clone(); + + // Validators (share indices) are not unique + let duplicated_index = 0; + validators.insert(duplicated_index, me.clone()); + + // And because of that the DKG should fail + let result = PubliclyVerifiableDkg::new( + &validators, + &DkgParams::new(0, security_threshold, shares_num).unwrap(), + &me, + ); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + Error::DuplicatedShareIndex(duplicated_index as u32).to_string() + ); + } /// Test that dealing correct PVSS transcripts /// pass verification an application and that @@ -432,11 +475,13 @@ mod test_dealing { } )); let pvss = dkg.share(rng).unwrap(); - let unknown_validator_i = dkg.dkg_params.shares_num + 1; + // Need to make sure this falls outside of the validator set: + let unknown_validator_index = + dkg.dkg_params.shares_num + VALIDATORS_NUM + 1; let sender = Validator:: { - address: gen_address(unknown_validator_i as usize), + address: gen_address(unknown_validator_index as usize), public_key: ferveo_common::Keypair::::new(rng).public_key(), - share_index: dkg.dkg_params.shares_num + 5, // Not in the validator set + share_index: unknown_validator_index, }; // check that verification fails assert!(dkg.verify_message(&sender, &pvss).is_err()); @@ -586,23 +631,35 @@ mod test_dealing { #[cfg(test)] mod test_aggregation { use ark_ec::AffineRepr; + use test_case::test_case; use crate::{dkg::*, test_common::*, DkgState, Message}; - /// Test that if the security threshold is - /// met, we can create a final key - #[test] - fn test_aggregate() { - let (mut dkg, _) = setup_dealt_dkg(); - let aggregate = dkg.aggregate().unwrap(); + /// Test that if the security threshold is met, we can create a final key + #[test_case(4,4; "number of validators equal to the number of shares")] + #[test_case(4,6; "number of validators greater than the number of shares")] + fn test_aggregate(shares_num: u32, validators_num: u32) { + let security_threshold = shares_num - 1; + let (mut dkg, _) = setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); + let aggregate_msg = dkg.aggregate().unwrap(); + if let Message::Aggregate(Aggregation { public_key, .. }) = + &aggregate_msg + { + assert_eq!(public_key, &dkg.public_key()); + } else { + panic!("Expected aggregate message") + } let sender = dkg.me.clone(); - assert!(dkg.verify_message(&sender, &aggregate).is_ok()); - assert!(dkg.apply_message(&sender, &aggregate).is_ok()); + assert!(dkg.verify_message(&sender, &aggregate_msg).is_ok()); + assert!(dkg.apply_message(&sender, &aggregate_msg).is_ok()); assert!(matches!(dkg.state, DkgState::Success { .. })); } - /// Test that aggregate only succeeds if we are in - /// the state [`DkgState::Dealt] + /// Test that aggregate only succeeds if we are in the state [`DkgState::Dealt] #[test] fn test_aggregate_state_guards() { let (mut dkg, _) = setup_dealt_dkg(); @@ -617,9 +674,8 @@ mod test_aggregation { assert!(dkg.aggregate().is_err()); } - /// Test that aggregate message fail to be verified - /// or applied unless dkg.state is - /// [`DkgState::Dealt`] + /// Test that aggregate message fail to be verified or applied unless + /// dkg.state is [`DkgState::Dealt`] #[test] fn test_aggregate_message_state_guards() { let (mut dkg, _) = setup_dealt_dkg(); diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index a7e5e7a4..f9d6c1a5 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -117,6 +117,10 @@ pub enum Error { /// Creating a transcript aggregate requires at least one transcript #[error("No transcripts to aggregate")] NoTranscriptsToAggregate, + + /// The number of messages may not be greater than the number of validators + #[error("Invalid aggregate verification parameters: number of validators {0}, number of messages: {1}")] + InvalidAggregateVerificationParameters(u32, u32), } pub type Result = std::result::Result; @@ -195,14 +199,18 @@ mod test_dkg_full { (pvss_aggregated, decryption_shares, shared_secret) } - #[test_case(4; "number of shares (validators) is a power of 2")] - #[test_case(7; "number of shares (validators) is not a power of 2")] - fn test_dkg_simple_tdec(shares_num: u32) { + #[test_case(4, 4; "number of shares (validators) is a power of 2")] + #[test_case(7, 7; "number of shares (validators) is not a power of 2")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn test_dkg_simple_tdec(shares_num: u32, validators_num: u32) { let rng = &mut test_rng(); - let threshold = shares_num / 2 + 1; - let (dkg, validator_keypairs) = - setup_dealt_dkg_with(threshold, shares_num); + let security_threshold = shares_num / 2 + 1; + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); let public_key = dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( @@ -230,15 +238,19 @@ mod test_dkg_full { assert_eq!(plaintext, MSG); } - #[test_case(4; "number of shares (validators) is a power of 2")] - #[test_case(7; "number of shares (validators) is not a power of 2")] - fn test_dkg_simple_tdec_precomputed(shares_num: u32) { + #[test_case(4, 4; "number of shares (validators) is a power of 2")] + #[test_case(7, 7; "number of shares (validators) is not a power of 2")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn test_dkg_simple_tdec_precomputed(shares_num: u32, validators_num: u32) { let rng = &mut test_rng(); // In precomputed variant, threshold must be equal to shares_num - let threshold = shares_num; - let (dkg, validator_keypairs) = - setup_dealt_dkg_with(threshold, shares_num); + let security_threshold = shares_num; + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); let public_key = dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), @@ -293,12 +305,20 @@ mod test_dkg_full { assert_eq!(plaintext, MSG); } - #[test] - fn test_dkg_simple_tdec_share_verification() { + #[test_case(4, 4; "number of validators equal to the number of shares")] + #[test_case(4, 6; "number of validators greater than the number of shares")] + fn test_dkg_simple_tdec_share_verification( + shares_num: u32, + validators_num: u32, + ) { let rng = &mut test_rng(); + let security_threshold = shares_num / 2 + 1; - let (dkg, validator_keypairs) = - setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM); + let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); let public_key = dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( SecretBox::new(MSG.to_vec()), @@ -389,7 +409,7 @@ mod test_dkg_full { // dkg.vss.remove(&removed_validator_addr); // TODO: Test whether it makes any difference // Remember to remove one domain point too - let mut domain_points = dkg.domain.elements().collect::>(); + let mut domain_points = dkg.domain_points(); domain_points.pop().unwrap(); // Now, we're going to recover a new share at a random point, @@ -541,15 +561,13 @@ mod test_dkg_full { validator_keypairs.as_slice(), ); - let domain_points = dkg.domain.elements().collect::>(); - // Each participant prepares an update for each other participant let share_updates = dkg .validators .keys() .map(|v_addr| { let deltas_i = prepare_share_updates_for_refresh::( - &domain_points, + &dkg.domain_points(), &dkg.pvss_params.h.into_affine(), dkg.dkg_params.security_threshold() as usize, rng, @@ -612,7 +630,7 @@ mod test_dkg_full { .collect(); let lagrange = ferveo_tdec::prepare_combine_simple::( - &domain_points[..SECURITY_THRESHOLD as usize], + &dkg.domain_points()[..SECURITY_THRESHOLD as usize], ); let new_shared_secret = ferveo_tdec::share_combine_simple::( &decryption_shares[..SECURITY_THRESHOLD as usize], diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 07328620..5108f6b9 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -4,7 +4,7 @@ use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup, Group}; use ark_ff::{Field, Zero}; use ark_poly::{ polynomial::univariate::DensePolynomial, DenseUVPolynomial, - EvaluationDomain, + EvaluationDomain, Polynomial, }; use ferveo_tdec::{ prepare_combine_simple, CiphertextHeader, DecryptionSharePrecomputed, @@ -140,7 +140,13 @@ impl PubliclyVerifiableSS { ); // Evaluations of the polynomial over the domain - let evals = phi.0.evaluate_over_domain_by_ref(dkg.domain); + let evals = dkg + .domain_points() + .iter() + .map(|x| phi.0.evaluate(x)) + .collect::>(); + debug_assert_eq!(evals.len(), dkg.validators.len()); + // commitment to coeffs, F_i let coeffs = fast_multiexp(&phi.0.coeffs, dkg.pvss_params.g); let shares = dkg @@ -148,9 +154,10 @@ impl PubliclyVerifiableSS { .values() .map(|validator| { // ek_{i}^{eval_i}, i = validator index + // TODO: Replace with regular, single-element exponentiation fast_multiexp( // &evals.evals[i..i] = &evals.evals[i] - &[evals.evals[validator.share_index as usize]], // one share per validator + &[evals[validator.share_index as usize]], // one share per validator validator.public_key.encryption_key.into_group(), )[0] }) @@ -262,7 +269,7 @@ pub fn do_verify_aggregation( // Now, we verify that the aggregated PVSS transcript is a valid aggregation let mut y = E::G1::zero(); - for (_, pvss) in vss.iter() { + for pvss in vss.values() { y += pvss.coeffs[0].into_group(); } if y.into_affine() == pvss_agg_coefficients[0] { @@ -276,7 +283,7 @@ pub fn do_verify_aggregation( impl PubliclyVerifiableSS { /// Verify that this PVSS instance is a valid aggregation of /// the PVSS instances, produced by [`aggregate`], - /// and received by the DKG context `dkg` + /// and received by the DKG context `dkg`. /// Returns the total nr of shares in the aggregated PVSS pub fn verify_aggregation( &self, @@ -393,9 +400,9 @@ pub(crate) fn aggregate( let mut shares = batch_to_projective_g2::(&first_pvss.shares); - // So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their sigma + // So now we're iterating over the PVSS instances, and adding their coefficients and shares, and their // sigma is the sum of all the sigma_i, which is the proof of knowledge of the secret polynomial - // Aggregating is just adding the corresponding values in pvss instances, so pvss = pvss + pvss_j + // Aggregating is just adding the corresponding values in PVSS instances, so PVSS = PVSS + PVSS_i for next_pvss in pvss_iter { sigma = (sigma + next_pvss.sigma).into(); coeffs @@ -422,16 +429,24 @@ mod test_pvss { use ark_bls12_381::Bls12_381 as EllipticCurve; use ark_ec::AffineRepr; use ark_ff::UniformRand; + use test_case::test_case; use super::*; - use crate::{test_common::*, DkgParams}; + use crate::test_common::*; - /// Test the happy flow that a pvss with the correct form is created + /// Test the happy flow such that the PVSS with the correct form is created /// and that appropriate validations pass - #[test] - fn test_new_pvss() { + #[test_case(4,4; "number of validators is equal to the number of shares")] + #[test_case(4,6; "number of validators is greater than the number of shares")] + fn test_new_pvss(shares_num: u32, validators_num: u32) { let rng = &mut ark_std::test_rng(); - let (dkg, _) = setup_dkg(0); + let security_threshold = shares_num - 1; + + let (dkg, _) = setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, + ); let s = ScalarField::rand(rng); let pvss = PubliclyVerifiableSS::::new(&s, &dkg, rng) .expect("Test failed"); @@ -453,7 +468,7 @@ mod test_pvss { } /// Check that if the proof of knowledge is wrong, - /// the optimistic verification of PVSS fails + /// then the optimistic verification of the PVSS fails #[test] fn test_verify_pvss_wrong_proof_of_knowledge() { let rng = &mut ark_std::test_rng(); @@ -494,39 +509,17 @@ mod test_pvss { assert!(!bad_pvss.verify_full(&dkg)); } - // TODO: Move this code to dkg.rs - /// Check that the canonical share indices of validators are expected and enforced - /// by the DKG methods. - #[test] - fn test_canonical_share_indices_are_enforced() { - let shares_num = 4; + /// Check that happy flow of aggregating PVSS transcripts + /// has the correct form and it's validations passes + #[test_case(4,4; "number of validators is equal to the number of shares")] + #[test_case(4,6; "number of validators is greater than the number of shares")] + fn test_aggregate_pvss(shares_num: u32, validators_num: u32) { let security_threshold = shares_num - 1; - let keypairs = gen_keypairs(shares_num); - let mut validators = gen_validators(&keypairs); - let me = validators[0].clone(); - - // Validators (share indices) are not unique - let duplicated_index = 0; - validators.insert(duplicated_index, me.clone()); - - // And because of that the DKG should fail - let result = PubliclyVerifiableDkg::new( - &validators, - &DkgParams::new(0, security_threshold, shares_num).unwrap(), - &me, + let (dkg, _) = setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + validators_num, ); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - Error::DuplicatedShareIndex(duplicated_index as u32).to_string() - ); - } - - /// Check that happy flow of aggregating PVSS transcripts - /// Should have the correct form and validations pass - #[test] - fn test_aggregate_pvss() { - let (dkg, _) = setup_dealt_dkg(); let pvss_list = dkg.vss.values().cloned().collect::>(); let aggregate = aggregate(&pvss_list).unwrap(); // Check that a polynomial of the correct degree was created diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index 524e6569..b02eba3b 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -118,7 +118,6 @@ pub fn make_random_polynomial_with_root( #[cfg(test)] mod tests_refresh { - use std::collections::HashMap; use ark_bls12_381::Fr; @@ -316,7 +315,6 @@ mod tests_refresh { /// The output is M new shares (with M <= Ñ), with each of the M new shares substituting the /// original share (i.e., the original share is deleted). #[test_matrix([4, 7, 11, 16])] - fn tdec_simple_variant_share_refreshing(shares_num: usize) { let rng = &mut test_rng(); let threshold = shares_num * 2 / 3; diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs index 4faae733..dce10e5d 100644 --- a/ferveo/src/test_common.rs +++ b/ferveo/src/test_common.rs @@ -18,6 +18,7 @@ pub const MSG: &[u8] = b"my-msg"; pub const AAD: &[u8] = b"my-aad"; pub const SECURITY_THRESHOLD: u32 = 3; pub const SHARES_NUM: u32 = 4; +pub const VALIDATORS_NUM: u32 = SHARES_NUM + 2; pub fn gen_keypairs(n: u32) -> Vec> { let rng = &mut ark_std::test_rng(); @@ -46,8 +47,9 @@ pub fn setup_dkg_for_n_validators( security_threshold: u32, shares_num: u32, my_validator_index: usize, + validators_num: u32, ) -> TestSetup { - let keypairs = gen_keypairs(shares_num); + let keypairs = gen_keypairs(validators_num); let validators = gen_validators(keypairs.as_slice()); let me = validators[my_validator_index].clone(); let dkg = PubliclyVerifiableDkg::new( @@ -67,6 +69,7 @@ pub fn setup_dkg(my_validator_index: usize) -> TestSetup { SECURITY_THRESHOLD, SHARES_NUM, my_validator_index, + VALIDATORS_NUM, ) } @@ -80,16 +83,29 @@ pub fn setup_dealt_dkg() -> TestSetup { pub fn setup_dealt_dkg_with( security_threshold: u32, shares_num: u32, +) -> TestSetup { + setup_dealt_dkg_with_n_validators( + security_threshold, + shares_num, + shares_num, + ) +} + +pub fn setup_dealt_dkg_with_n_validators( + security_threshold: u32, + shares_num: u32, + validators_num: u32, ) -> TestSetup { let rng = &mut ark_std::test_rng(); // Gather everyone's transcripts - let mut messages: Vec<_> = (0..shares_num) + let mut messages: Vec<_> = (0..validators_num) .map(|my_index| { let (mut dkg, _) = setup_dkg_for_n_validators( security_threshold, shares_num, my_index as usize, + validators_num, ); let me = dkg.me.clone(); let message = dkg.share(rng).unwrap(); @@ -98,8 +114,12 @@ pub fn setup_dealt_dkg_with( .collect(); // Create a test DKG instance - let (mut dkg, keypairs) = - setup_dkg_for_n_validators(security_threshold, shares_num, 0); + let (mut dkg, keypairs) = setup_dkg_for_n_validators( + security_threshold, + shares_num, + 0, + validators_num, + ); // The ordering of messages should not matter messages.shuffle(rng);