charm-crypto-framework 0.61.1__cp313-cp313-macosx_10_13_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- charm/__init__.py +5 -0
- charm/adapters/__init__.py +0 -0
- charm/adapters/abenc_adapt_hybrid.py +90 -0
- charm/adapters/dabenc_adapt_hybrid.py +145 -0
- charm/adapters/ibenc_adapt_hybrid.py +72 -0
- charm/adapters/ibenc_adapt_identityhash.py +80 -0
- charm/adapters/kpabenc_adapt_hybrid.py +91 -0
- charm/adapters/pkenc_adapt_bchk05.py +121 -0
- charm/adapters/pkenc_adapt_chk04.py +91 -0
- charm/adapters/pkenc_adapt_hybrid.py +98 -0
- charm/adapters/pksig_adapt_naor01.py +89 -0
- charm/config.py +7 -0
- charm/core/__init__.py +0 -0
- charm/core/benchmark/benchmark_util.c +353 -0
- charm/core/benchmark/benchmark_util.h +61 -0
- charm/core/benchmark/benchmarkmodule.c +476 -0
- charm/core/benchmark/benchmarkmodule.h +162 -0
- charm/core/benchmark.cpython-313-darwin.so +0 -0
- charm/core/crypto/AES/AES.c +1464 -0
- charm/core/crypto/AES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES/DES.c +113 -0
- charm/core/crypto/DES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES3/DES3.c +26 -0
- charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
- charm/core/crypto/__init__.py +0 -0
- charm/core/crypto/cryptobase/XOR.c +80 -0
- charm/core/crypto/cryptobase/_counter.c +496 -0
- charm/core/crypto/cryptobase/_counter.h +54 -0
- charm/core/crypto/cryptobase/block_template.c +900 -0
- charm/core/crypto/cryptobase/block_template.h +69 -0
- charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
- charm/core/crypto/cryptobase/stream_template.c +271 -0
- charm/core/crypto/cryptobase/strxor.c +229 -0
- charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
- charm/core/engine/__init__.py +5 -0
- charm/core/engine/protocol.py +293 -0
- charm/core/engine/util.py +174 -0
- charm/core/math/__init__.py +0 -0
- charm/core/math/elliptic_curve/ecmodule.c +1986 -0
- charm/core/math/elliptic_curve/ecmodule.h +230 -0
- charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
- charm/core/math/elliptic_curve.pyi +63 -0
- charm/core/math/integer/integermodule.c +2539 -0
- charm/core/math/integer/integermodule.h +145 -0
- charm/core/math/integer.cpython-313-darwin.so +0 -0
- charm/core/math/integer.pyi +76 -0
- charm/core/math/pairing/miracl/miracl_config.h +37 -0
- charm/core/math/pairing/miracl/miracl_interface.h +118 -0
- charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
- charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
- charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
- charm/core/math/pairing/pairingmodule.c +2230 -0
- charm/core/math/pairing/pairingmodule.h +241 -0
- charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
- charm/core/math/pairing/relic/pairingmodule3.h +233 -0
- charm/core/math/pairing/relic/relic_interface.c +1337 -0
- charm/core/math/pairing/relic/relic_interface.h +217 -0
- charm/core/math/pairing/relic/test_relic.c +171 -0
- charm/core/math/pairing.cpython-313-darwin.so +0 -0
- charm/core/math/pairing.pyi +69 -0
- charm/core/utilities/base64.c +248 -0
- charm/core/utilities/base64.h +15 -0
- charm/schemes/__init__.py +0 -0
- charm/schemes/abenc/__init__.py +0 -0
- charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
- charm/schemes/abenc/abenc_bsw07.py +146 -0
- charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
- charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
- charm/schemes/abenc/abenc_lsw08.py +159 -0
- charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
- charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
- charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
- charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
- charm/schemes/abenc/abenc_waters09.py +144 -0
- charm/schemes/abenc/abenc_yct14.py +208 -0
- charm/schemes/abenc/abenc_yllc15.py +178 -0
- charm/schemes/abenc/ac17.py +248 -0
- charm/schemes/abenc/bsw07.py +141 -0
- charm/schemes/abenc/cgw15.py +277 -0
- charm/schemes/abenc/dabe_aw11.py +204 -0
- charm/schemes/abenc/dfa_fe12.py +144 -0
- charm/schemes/abenc/pk_hve08.py +179 -0
- charm/schemes/abenc/waters11.py +143 -0
- charm/schemes/aggrsign_MuSig.py +150 -0
- charm/schemes/aggrsign_bls.py +267 -0
- charm/schemes/blindsig_ps16.py +654 -0
- charm/schemes/chamhash_adm05.py +113 -0
- charm/schemes/chamhash_rsa_hw09.py +100 -0
- charm/schemes/commit/__init__.py +0 -0
- charm/schemes/commit/commit_gs08.py +77 -0
- charm/schemes/commit/commit_pedersen92.py +53 -0
- charm/schemes/encap_bchk05.py +62 -0
- charm/schemes/grpsig/__init__.py +0 -0
- charm/schemes/grpsig/groupsig_bgls04.py +114 -0
- charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
- charm/schemes/hibenc/__init__.py +0 -0
- charm/schemes/hibenc/hibenc_bb04.py +105 -0
- charm/schemes/hibenc/hibenc_lew11.py +193 -0
- charm/schemes/ibenc/__init__.py +0 -0
- charm/schemes/ibenc/clpkc_rp03.py +119 -0
- charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
- charm/schemes/ibenc/ibenc_bb03.py +94 -0
- charm/schemes/ibenc/ibenc_bf01.py +121 -0
- charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
- charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
- charm/schemes/ibenc/ibenc_lsw08.py +120 -0
- charm/schemes/ibenc/ibenc_sw05.py +238 -0
- charm/schemes/ibenc/ibenc_waters05.py +144 -0
- charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
- charm/schemes/ibenc/ibenc_waters09.py +107 -0
- charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
- charm/schemes/joye_scheme.py +106 -0
- charm/schemes/lem_scheme.py +207 -0
- charm/schemes/pk_fre_ccv11.py +107 -0
- charm/schemes/pk_vrf.py +127 -0
- charm/schemes/pkenc/__init__.py +0 -0
- charm/schemes/pkenc/pkenc_cs98.py +108 -0
- charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
- charm/schemes/pkenc/pkenc_gm82.py +98 -0
- charm/schemes/pkenc/pkenc_paillier99.py +118 -0
- charm/schemes/pkenc/pkenc_rabin.py +254 -0
- charm/schemes/pkenc/pkenc_rsa.py +186 -0
- charm/schemes/pksig/__init__.py +0 -0
- charm/schemes/pksig/pksig_CW13_z.py +135 -0
- charm/schemes/pksig/pksig_bls04.py +87 -0
- charm/schemes/pksig/pksig_boyen.py +156 -0
- charm/schemes/pksig/pksig_chch.py +97 -0
- charm/schemes/pksig/pksig_chp.py +70 -0
- charm/schemes/pksig/pksig_cl03.py +150 -0
- charm/schemes/pksig/pksig_cl04.py +87 -0
- charm/schemes/pksig/pksig_cllww12_z.py +142 -0
- charm/schemes/pksig/pksig_cyh.py +132 -0
- charm/schemes/pksig/pksig_dsa.py +76 -0
- charm/schemes/pksig/pksig_ecdsa.py +71 -0
- charm/schemes/pksig/pksig_hess.py +104 -0
- charm/schemes/pksig/pksig_hw.py +110 -0
- charm/schemes/pksig/pksig_lamport.py +63 -0
- charm/schemes/pksig/pksig_ps01.py +135 -0
- charm/schemes/pksig/pksig_ps02.py +124 -0
- charm/schemes/pksig/pksig_ps03.py +119 -0
- charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
- charm/schemes/pksig/pksig_schnorr91.py +77 -0
- charm/schemes/pksig/pksig_waters.py +115 -0
- charm/schemes/pksig/pksig_waters05.py +121 -0
- charm/schemes/pksig/pksig_waters09.py +121 -0
- charm/schemes/pre_mg07.py +150 -0
- charm/schemes/prenc/pre_afgh06.py +126 -0
- charm/schemes/prenc/pre_bbs98.py +123 -0
- charm/schemes/prenc/pre_nal16.py +216 -0
- charm/schemes/protocol_a01.py +272 -0
- charm/schemes/protocol_ao00.py +215 -0
- charm/schemes/protocol_cns07.py +274 -0
- charm/schemes/protocol_schnorr91.py +125 -0
- charm/schemes/sigma1.py +64 -0
- charm/schemes/sigma2.py +129 -0
- charm/schemes/sigma3.py +126 -0
- charm/schemes/threshold/__init__.py +59 -0
- charm/schemes/threshold/dkls23_dkg.py +556 -0
- charm/schemes/threshold/dkls23_presign.py +1089 -0
- charm/schemes/threshold/dkls23_sign.py +761 -0
- charm/schemes/threshold/xrpl_wallet.py +967 -0
- charm/test/__init__.py +0 -0
- charm/test/adapters/__init__.py +0 -0
- charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
- charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
- charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
- charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
- charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
- charm/test/benchmark/abenc_yllc15_bench.py +92 -0
- charm/test/benchmark/benchmark_test.py +148 -0
- charm/test/benchmark_threshold.py +260 -0
- charm/test/conftest.py +38 -0
- charm/test/fuzz/__init__.py +1 -0
- charm/test/fuzz/conftest.py +5 -0
- charm/test/fuzz/fuzz_policy_parser.py +76 -0
- charm/test/fuzz/fuzz_serialization.py +83 -0
- charm/test/schemes/__init__.py +0 -0
- charm/test/schemes/abenc/__init__.py +0 -0
- charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
- charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
- charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
- charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
- charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
- charm/test/schemes/chamhash_adm05_test.py +31 -0
- charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
- charm/test/schemes/commit/__init__.py +0 -0
- charm/test/schemes/commit/commit_gs08_test.py +24 -0
- charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
- charm/test/schemes/dabe_aw11_test.py +45 -0
- charm/test/schemes/encap_bchk05_test.py +21 -0
- charm/test/schemes/grpsig/__init__.py +0 -0
- charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
- charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
- charm/test/schemes/hibenc/__init__.py +0 -0
- charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
- charm/test/schemes/ibenc/__init__.py +0 -0
- charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
- charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
- charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
- charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
- charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
- charm/test/schemes/pk_vrf_test.py +29 -0
- charm/test/schemes/pkenc/__init__.py +0 -0
- charm/test/schemes/pkenc_test.py +255 -0
- charm/test/schemes/pksig/__init__.py +0 -0
- charm/test/schemes/pksig_test.py +376 -0
- charm/test/schemes/rsa_alg_test.py +340 -0
- charm/test/schemes/threshold_test.py +1792 -0
- charm/test/serialize/__init__.py +0 -0
- charm/test/serialize/serialize_test.py +40 -0
- charm/test/toolbox/__init__.py +0 -0
- charm/test/toolbox/conversion_test.py +30 -0
- charm/test/toolbox/ecgroup_test.py +53 -0
- charm/test/toolbox/integer_arithmetic_test.py +441 -0
- charm/test/toolbox/paddingschemes_test.py +238 -0
- charm/test/toolbox/policy_parser_stress_test.py +969 -0
- charm/test/toolbox/secretshare_test.py +28 -0
- charm/test/toolbox/symcrypto_test.py +108 -0
- charm/test/toolbox/test_policy_expression.py +16 -0
- charm/test/vectors/__init__.py +1 -0
- charm/test/vectors/test_bls_vectors.py +289 -0
- charm/test/vectors/test_pedersen_vectors.py +315 -0
- charm/test/vectors/test_schnorr_vectors.py +368 -0
- charm/test/zkp_compiler/__init__.py +9 -0
- charm/test/zkp_compiler/benchmark_zkp.py +258 -0
- charm/test/zkp_compiler/test_and_proof.py +240 -0
- charm/test/zkp_compiler/test_batch_verify.py +248 -0
- charm/test/zkp_compiler/test_dleq_proof.py +264 -0
- charm/test/zkp_compiler/test_or_proof.py +231 -0
- charm/test/zkp_compiler/test_proof_serialization.py +121 -0
- charm/test/zkp_compiler/test_range_proof.py +241 -0
- charm/test/zkp_compiler/test_representation_proof.py +325 -0
- charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
- charm/test/zkp_compiler/test_thread_safety.py +169 -0
- charm/test/zkp_compiler/test_zkp_parser.py +139 -0
- charm/toolbox/ABEnc.py +26 -0
- charm/toolbox/ABEncMultiAuth.py +66 -0
- charm/toolbox/ABEnumeric.py +800 -0
- charm/toolbox/Commit.py +24 -0
- charm/toolbox/DFA.py +89 -0
- charm/toolbox/FSA.py +1254 -0
- charm/toolbox/Hash.py +39 -0
- charm/toolbox/IBEnc.py +62 -0
- charm/toolbox/IBSig.py +64 -0
- charm/toolbox/PKEnc.py +66 -0
- charm/toolbox/PKSig.py +56 -0
- charm/toolbox/PREnc.py +32 -0
- charm/toolbox/ZKProof.py +289 -0
- charm/toolbox/__init__.py +0 -0
- charm/toolbox/bitstring.py +49 -0
- charm/toolbox/broadcast.py +220 -0
- charm/toolbox/conversion.py +100 -0
- charm/toolbox/eccurve.py +149 -0
- charm/toolbox/ecgroup.py +143 -0
- charm/toolbox/enum.py +60 -0
- charm/toolbox/hash_module.py +91 -0
- charm/toolbox/integergroup.py +323 -0
- charm/toolbox/iterate.py +22 -0
- charm/toolbox/matrixops.py +76 -0
- charm/toolbox/mpc_utils.py +296 -0
- charm/toolbox/msp.py +175 -0
- charm/toolbox/mta.py +985 -0
- charm/toolbox/node.py +120 -0
- charm/toolbox/ot/__init__.py +22 -0
- charm/toolbox/ot/base_ot.py +374 -0
- charm/toolbox/ot/dpf.py +642 -0
- charm/toolbox/ot/mpfss.py +228 -0
- charm/toolbox/ot/ot_extension.py +589 -0
- charm/toolbox/ot/silent_ot.py +378 -0
- charm/toolbox/paddingschemes.py +423 -0
- charm/toolbox/paddingschemes_test.py +238 -0
- charm/toolbox/pairingcurves.py +85 -0
- charm/toolbox/pairinggroup.py +186 -0
- charm/toolbox/policy_expression_spec.py +70 -0
- charm/toolbox/policytree.py +189 -0
- charm/toolbox/reCompiler.py +346 -0
- charm/toolbox/redundancyschemes.py +65 -0
- charm/toolbox/schemebase.py +188 -0
- charm/toolbox/secretshare.py +104 -0
- charm/toolbox/secretutil.py +174 -0
- charm/toolbox/securerandom.py +73 -0
- charm/toolbox/sigmaprotocol.py +46 -0
- charm/toolbox/specialprimes.py +45 -0
- charm/toolbox/symcrypto.py +279 -0
- charm/toolbox/threshold_sharing.py +553 -0
- charm/toolbox/xmlserialize.py +94 -0
- charm/toolbox/zknode.py +105 -0
- charm/zkp_compiler/__init__.py +89 -0
- charm/zkp_compiler/and_proof.py +460 -0
- charm/zkp_compiler/batch_verify.py +324 -0
- charm/zkp_compiler/dleq_proof.py +423 -0
- charm/zkp_compiler/or_proof.py +305 -0
- charm/zkp_compiler/range_proof.py +417 -0
- charm/zkp_compiler/representation_proof.py +466 -0
- charm/zkp_compiler/schnorr_proof.py +273 -0
- charm/zkp_compiler/thread_safe.py +150 -0
- charm/zkp_compiler/zk_demo.py +489 -0
- charm/zkp_compiler/zkp_factory.py +330 -0
- charm/zkp_compiler/zkp_generator.py +370 -0
- charm/zkp_compiler/zkparser.py +269 -0
- charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
- charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
- charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
- charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
- charm_crypto_framework-0.61.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Distributed Key Generation for DKLS23 Threshold ECDSA
|
|
3
|
+
|
|
4
|
+
| From: "Two-Round Threshold ECDSA from ECDSA Assumptions"
|
|
5
|
+
| By: Jack Doerner, Yashvanth Kondi, Eysa Lee, abhi shelat
|
|
6
|
+
| Published: IEEE S&P 2023
|
|
7
|
+
| URL: https://eprint.iacr.org/2023/765
|
|
8
|
+
|
|
9
|
+
* type: distributed key generation
|
|
10
|
+
* setting: Elliptic Curve DDH-hard group
|
|
11
|
+
* assumption: DDH
|
|
12
|
+
|
|
13
|
+
This module implements a distributed key generation (DKG) protocol for
|
|
14
|
+
threshold ECDSA as described in DKLS23. Uses Feldman VSS for verifiable
|
|
15
|
+
secret sharing, compatible with secp256k1 curve.
|
|
16
|
+
|
|
17
|
+
:Authors: Elton de Souza
|
|
18
|
+
:Date: 01/2026
|
|
19
|
+
'''
|
|
20
|
+
|
|
21
|
+
from charm.toolbox.ecgroup import ECGroup, ZR, G
|
|
22
|
+
from charm.toolbox.eccurve import secp256k1
|
|
23
|
+
from charm.toolbox.threshold_sharing import ThresholdSharing, PedersenVSS
|
|
24
|
+
from charm.toolbox.broadcast import EchoBroadcast
|
|
25
|
+
from charm.core.engine.protocol import Protocol
|
|
26
|
+
from typing import Dict, List, Tuple, Optional, Any, Set
|
|
27
|
+
|
|
28
|
+
# Type aliases for charm-crypto types
|
|
29
|
+
ZRElement = Any # Scalar field element
|
|
30
|
+
GElement = Any # Group/curve point element
|
|
31
|
+
ECGroupType = Any # ECGroup instance
|
|
32
|
+
PartyId = int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class KeyShare:
|
|
36
|
+
"""
|
|
37
|
+
Holds a party's key share for threshold ECDSA
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
party_id: The party's identifier (1 to n)
|
|
41
|
+
x_i: Party's share of the private key
|
|
42
|
+
X: Combined public key (g^x where x = sum of all secrets)
|
|
43
|
+
X_i: Verification key for this party (g^{x_i})
|
|
44
|
+
t: Threshold parameter
|
|
45
|
+
n: Total number of parties
|
|
46
|
+
|
|
47
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
48
|
+
>>> group = ECGroup(secp256k1)
|
|
49
|
+
>>> g = group.random(G)
|
|
50
|
+
>>> private_share = group.random(ZR)
|
|
51
|
+
>>> public_key = g ** private_share
|
|
52
|
+
>>> verification_key = g ** private_share
|
|
53
|
+
>>> ks = KeyShare(1, private_share, public_key, verification_key, 2, 3)
|
|
54
|
+
>>> ks.party_id
|
|
55
|
+
1
|
|
56
|
+
>>> ks.t
|
|
57
|
+
2
|
|
58
|
+
>>> ks.n
|
|
59
|
+
3
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, party_id: PartyId, private_share: ZRElement, public_key: GElement, verification_key: GElement, threshold: int, num_parties: int) -> None:
|
|
63
|
+
self.party_id = party_id
|
|
64
|
+
self.x_i = private_share # Party's share of private key
|
|
65
|
+
self.X = public_key # Combined public key
|
|
66
|
+
self.X_i = verification_key # g^{x_i} for verification
|
|
67
|
+
self.t = threshold
|
|
68
|
+
self.n = num_parties
|
|
69
|
+
|
|
70
|
+
def __repr__(self) -> str:
|
|
71
|
+
return f"KeyShare(party_id={self.party_id}, t={self.t}, n={self.n})"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DKLS23_DKG:
|
|
75
|
+
"""
|
|
76
|
+
Distributed Key Generation for DKLS23 Threshold ECDSA
|
|
77
|
+
|
|
78
|
+
Generates threshold ECDSA keys where t-of-n parties are required to sign.
|
|
79
|
+
Uses Feldman VSS for verifiable secret sharing.
|
|
80
|
+
|
|
81
|
+
Curve Agnostic
|
|
82
|
+
--------------
|
|
83
|
+
This implementation supports any elliptic curve group that is DDH-hard.
|
|
84
|
+
The curve is specified via the groupObj parameter.
|
|
85
|
+
|
|
86
|
+
Protocol:
|
|
87
|
+
1. Round 1: Each party i samples random polynomial f_i(x) of degree t-1
|
|
88
|
+
with f_i(0) = s_i (their secret). Broadcasts Feldman commitments
|
|
89
|
+
C_{i,j} = g^{a_{i,j}} for all coefficients.
|
|
90
|
+
|
|
91
|
+
2. Round 2: Each party i sends share f_i(j) to party j via secure channel.
|
|
92
|
+
|
|
93
|
+
3. Round 3: Each party j verifies received shares against commitments:
|
|
94
|
+
g^{f_i(j)} = prod C_{i,k}^{j^k}. Computes final share x_j = sum f_i(j)
|
|
95
|
+
and public key X = prod g^{s_i}.
|
|
96
|
+
|
|
97
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
98
|
+
>>> group = ECGroup(secp256k1)
|
|
99
|
+
>>> # Simulate 2-of-3 DKG
|
|
100
|
+
>>> dkg = DKLS23_DKG(group, threshold=2, num_parties=3)
|
|
101
|
+
>>> g = group.random(G)
|
|
102
|
+
>>>
|
|
103
|
+
>>> # Round 1: Each party generates secret and Feldman commitments
|
|
104
|
+
>>> party_states = [dkg.keygen_round1(i+1, g) for i in range(3)]
|
|
105
|
+
>>> round1_msgs = [state[0] for state in party_states]
|
|
106
|
+
>>> private_states = [state[1] for state in party_states]
|
|
107
|
+
>>>
|
|
108
|
+
>>> # All parties should have different secrets (compare as ints since ZR not hashable)
|
|
109
|
+
>>> len(set(int(s['secret']) for s in private_states)) == 3
|
|
110
|
+
True
|
|
111
|
+
>>>
|
|
112
|
+
>>> # Round 2: Generate shares for other parties
|
|
113
|
+
>>> round2_results = [dkg.keygen_round2(i+1, private_states[i], round1_msgs) for i in range(3)]
|
|
114
|
+
>>> shares_for_others = [r[0] for r in round2_results]
|
|
115
|
+
>>> states_after_round2 = [r[1] for r in round2_results]
|
|
116
|
+
>>>
|
|
117
|
+
>>> # Collect shares received by each party from all parties
|
|
118
|
+
>>> received_shares = {}
|
|
119
|
+
>>> for receiver in range(1, 4):
|
|
120
|
+
... received_shares[receiver] = {}
|
|
121
|
+
... for sender in range(1, 4):
|
|
122
|
+
... received_shares[receiver][sender] = shares_for_others[sender-1][receiver]
|
|
123
|
+
>>>
|
|
124
|
+
>>> # Round 3: Verify shares and compute final key shares
|
|
125
|
+
>>> key_shares = [dkg.keygen_round3(i+1, states_after_round2[i], received_shares[i+1], round1_msgs) for i in range(3)]
|
|
126
|
+
>>>
|
|
127
|
+
>>> # All parties should have the same public key
|
|
128
|
+
>>> key_shares[0].X == key_shares[1].X == key_shares[2].X
|
|
129
|
+
True
|
|
130
|
+
>>>
|
|
131
|
+
>>> # Verification keys should be correct (g^{x_i})
|
|
132
|
+
>>> all(g ** ks.x_i == ks.X_i for ks in key_shares)
|
|
133
|
+
True
|
|
134
|
+
>>>
|
|
135
|
+
>>> # Public key should equal product of first commitments
|
|
136
|
+
>>> computed_pk = dkg.compute_public_key([msg['commitments'] for msg in round1_msgs], g)
|
|
137
|
+
>>> key_shares[0].X == computed_pk
|
|
138
|
+
True
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(self, groupObj: ECGroupType, threshold: int, num_parties: int) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Initialize the DKG protocol
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
groupObj: An ECGroup instance (e.g., ECGroup(secp256k1))
|
|
147
|
+
threshold: Minimum number of parties required to sign (t)
|
|
148
|
+
num_parties: Total number of parties (n)
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
ValueError: If groupObj is None, threshold > num_parties, or threshold < 1
|
|
152
|
+
"""
|
|
153
|
+
if groupObj is None:
|
|
154
|
+
raise ValueError("groupObj cannot be None")
|
|
155
|
+
if threshold > num_parties:
|
|
156
|
+
raise ValueError("threshold cannot exceed num_parties")
|
|
157
|
+
if threshold < 1:
|
|
158
|
+
raise ValueError("threshold must be at least 1")
|
|
159
|
+
|
|
160
|
+
self.group = groupObj
|
|
161
|
+
self.t = threshold
|
|
162
|
+
self.n = num_parties
|
|
163
|
+
self.order = groupObj.order()
|
|
164
|
+
self._sharing = ThresholdSharing(groupObj)
|
|
165
|
+
self._broadcast = EchoBroadcast(num_parties)
|
|
166
|
+
|
|
167
|
+
def keygen_round1(self, party_id: PartyId, generator: GElement, session_id: bytes) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
168
|
+
"""
|
|
169
|
+
Round 1: Each party generates secret share and Feldman commitments
|
|
170
|
+
|
|
171
|
+
Each party i samples a random polynomial f_i(x) of degree t-1 where
|
|
172
|
+
f_i(0) = s_i is their secret contribution. Then broadcasts Feldman
|
|
173
|
+
commitments C_{i,j} = g^{a_{i,j}} for all coefficients a_{i,0}, ..., a_{i,t-1}.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
party_id: This party's identifier (1 to n)
|
|
177
|
+
generator: Generator point g in the EC group
|
|
178
|
+
session_id: Required session identifier (bytes or str). Must be unique
|
|
179
|
+
per protocol instance and shared across all participants to prevent
|
|
180
|
+
replay attacks.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Tuple of (broadcast_msg, private_state)
|
|
184
|
+
- broadcast_msg: Dictionary containing party_id, session_id, and commitments
|
|
185
|
+
- private_state: Dictionary containing secret, coefficients, shares, and session_id
|
|
186
|
+
|
|
187
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
188
|
+
>>> group = ECGroup(secp256k1)
|
|
189
|
+
>>> dkg = DKLS23_DKG(group, threshold=2, num_parties=3)
|
|
190
|
+
>>> g = group.random(G)
|
|
191
|
+
>>> msg, state = dkg.keygen_round1(1, g, session_id=b"test-session")
|
|
192
|
+
>>> 'party_id' in msg and 'commitments' in msg
|
|
193
|
+
True
|
|
194
|
+
>>> len(msg['commitments']) == 2 # t commitments
|
|
195
|
+
True
|
|
196
|
+
>>> 'secret' in state and 'coefficients' in state
|
|
197
|
+
True
|
|
198
|
+
>>> 'session_id' in msg
|
|
199
|
+
True
|
|
200
|
+
"""
|
|
201
|
+
# Validate session_id is provided and non-empty
|
|
202
|
+
if session_id is None:
|
|
203
|
+
raise ValueError("session_id is required for replay attack prevention")
|
|
204
|
+
if isinstance(session_id, (bytes, str)) and len(session_id) == 0:
|
|
205
|
+
raise ValueError("session_id cannot be empty")
|
|
206
|
+
|
|
207
|
+
# Generate random secret for this party
|
|
208
|
+
secret = self.group.random(ZR)
|
|
209
|
+
|
|
210
|
+
# Generate random polynomial coefficients: a_0 = secret, a_1...a_{t-1} random
|
|
211
|
+
coeffs = [secret]
|
|
212
|
+
for _ in range(self.t - 1):
|
|
213
|
+
coeffs.append(self.group.random(ZR))
|
|
214
|
+
|
|
215
|
+
# Compute Feldman commitments: C_j = g^{a_j}
|
|
216
|
+
commitments = [generator ** coeff for coeff in coeffs]
|
|
217
|
+
|
|
218
|
+
# Pre-compute shares for all parties (to be sent in round 2)
|
|
219
|
+
shares = {}
|
|
220
|
+
for j in range(1, self.n + 1):
|
|
221
|
+
shares[j] = self._sharing._eval_polynomial(coeffs, j)
|
|
222
|
+
|
|
223
|
+
# Broadcast message (public)
|
|
224
|
+
broadcast_msg = {
|
|
225
|
+
'party_id': party_id,
|
|
226
|
+
'session_id': session_id,
|
|
227
|
+
'commitments': commitments
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Private state (kept secret by this party)
|
|
231
|
+
private_state = {
|
|
232
|
+
'party_id': party_id,
|
|
233
|
+
'session_id': session_id,
|
|
234
|
+
'secret': secret,
|
|
235
|
+
'coefficients': coeffs,
|
|
236
|
+
'shares': shares,
|
|
237
|
+
'generator': generator
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return broadcast_msg, private_state
|
|
241
|
+
|
|
242
|
+
def keygen_round2(self, party_id: PartyId, private_state: Dict[str, Any], all_round1_msgs: List[Dict[str, Any]]) -> Tuple[Dict[PartyId, ZRElement], Dict[str, Any]]:
|
|
243
|
+
"""
|
|
244
|
+
Round 2: Verify commitments, generate shares for each party
|
|
245
|
+
|
|
246
|
+
Each party verifies that received round 1 messages are well-formed,
|
|
247
|
+
then prepares shares f_i(j) to send to each party j via secure channel.
|
|
248
|
+
|
|
249
|
+
IMPORTANT: This function assumes an authenticated broadcast channel is used
|
|
250
|
+
for round 1 messages. In practice, this requires implementing echo broadcast
|
|
251
|
+
to ensure all parties received the same messages from each sender. See
|
|
252
|
+
verify_broadcast_consistency() for validating broadcast consistency.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
party_id: This party's identifier
|
|
256
|
+
private_state: Private state from round 1
|
|
257
|
+
all_round1_msgs: List of broadcast messages from all parties
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Tuple of (private_shares_for_others, updated_state)
|
|
261
|
+
- private_shares_for_others: Dict mapping recipient party_id to share
|
|
262
|
+
- updated_state: Updated private state
|
|
263
|
+
|
|
264
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
265
|
+
>>> group = ECGroup(secp256k1)
|
|
266
|
+
>>> dkg = DKLS23_DKG(group, threshold=2, num_parties=3)
|
|
267
|
+
>>> g = group.random(G)
|
|
268
|
+
>>> states = [dkg.keygen_round1(i+1, g) for i in range(3)]
|
|
269
|
+
>>> round1_msgs = [s[0] for s in states]
|
|
270
|
+
>>> shares, state = dkg.keygen_round2(1, states[0][1], round1_msgs)
|
|
271
|
+
>>> len(shares) == 3 # Shares for all parties
|
|
272
|
+
True
|
|
273
|
+
"""
|
|
274
|
+
# Verify we have messages from all parties
|
|
275
|
+
received_party_ids = set(msg['party_id'] for msg in all_round1_msgs)
|
|
276
|
+
expected_party_ids = set(range(1, self.n + 1))
|
|
277
|
+
if received_party_ids != expected_party_ids:
|
|
278
|
+
raise ValueError(f"Missing round 1 messages from parties: {expected_party_ids - received_party_ids}")
|
|
279
|
+
|
|
280
|
+
# Verify all commitments have correct length
|
|
281
|
+
for msg in all_round1_msgs:
|
|
282
|
+
if len(msg['commitments']) != self.t:
|
|
283
|
+
raise ValueError(f"Party {msg['party_id']} has {len(msg['commitments'])} commitments, expected {self.t}")
|
|
284
|
+
|
|
285
|
+
# Prepare shares to send to each party
|
|
286
|
+
# (These are the shares we computed in round 1)
|
|
287
|
+
shares_to_send = private_state['shares'].copy()
|
|
288
|
+
|
|
289
|
+
# Store round1 messages for verification in round 3
|
|
290
|
+
updated_state = private_state.copy()
|
|
291
|
+
updated_state['all_round1_msgs'] = {msg['party_id']: msg for msg in all_round1_msgs}
|
|
292
|
+
|
|
293
|
+
return shares_to_send, updated_state
|
|
294
|
+
|
|
295
|
+
def _verify_share_against_commitments(self, sender_id: PartyId, receiver_id: PartyId, share: ZRElement, commitments: List[GElement], generator: GElement) -> bool:
|
|
296
|
+
"""
|
|
297
|
+
Verify a received share against Feldman commitments
|
|
298
|
+
|
|
299
|
+
Checks: g^{share} == prod_{k=0}^{t-1} C_{sender,k}^{receiver^k}
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
sender_id: ID of the party who sent the share
|
|
303
|
+
receiver_id: ID of the party receiving the share
|
|
304
|
+
share: The share value to verify
|
|
305
|
+
commitments: List of Feldman commitments from sender
|
|
306
|
+
generator: Generator point g
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
True if share is valid, False otherwise
|
|
310
|
+
"""
|
|
311
|
+
return self._sharing.verify_share(receiver_id, share, commitments, generator)
|
|
312
|
+
|
|
313
|
+
def keygen_round3(self, party_id: PartyId, private_state: Dict[str, Any], received_shares: Dict[PartyId, ZRElement], all_round1_msgs: List[Dict[str, Any]]) -> Tuple[Optional['KeyShare'], Optional[Dict[str, Any]]]:
|
|
314
|
+
"""
|
|
315
|
+
Round 3: Verify received shares, compute final key share
|
|
316
|
+
|
|
317
|
+
Each party j verifies all received shares f_i(j) against the
|
|
318
|
+
Feldman commitments from round 1. If all shares verify, computes:
|
|
319
|
+
- Final share: x_j = sum_{i=1}^{n} f_i(j)
|
|
320
|
+
- Verification key: X_j = g^{x_j}
|
|
321
|
+
- Public key: X = prod_{i=1}^{n} C_{i,0} = g^{sum s_i}
|
|
322
|
+
|
|
323
|
+
If a share verification fails, instead of crashing, a complaint is
|
|
324
|
+
generated that can be used to identify malicious parties.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
party_id: This party's identifier
|
|
328
|
+
private_state: Private state from round 2
|
|
329
|
+
received_shares: Dict mapping sender party_id to share value
|
|
330
|
+
all_round1_msgs: List of broadcast messages from all parties
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Tuple of (KeyShare, complaint) where:
|
|
334
|
+
- KeyShare: The computed key share (or None if verification failed)
|
|
335
|
+
- complaint: Dict with 'accuser', 'accused', 'share', 'commitments' if
|
|
336
|
+
verification failed, or None if all shares verified
|
|
337
|
+
|
|
338
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
339
|
+
>>> group = ECGroup(secp256k1)
|
|
340
|
+
>>> dkg = DKLS23_DKG(group, threshold=2, num_parties=3)
|
|
341
|
+
>>> g = group.random(G)
|
|
342
|
+
>>> # Run full DKG
|
|
343
|
+
>>> party_states = [dkg.keygen_round1(i+1, g) for i in range(3)]
|
|
344
|
+
>>> round1_msgs = [s[0] for s in party_states]
|
|
345
|
+
>>> priv_states = [s[1] for s in party_states]
|
|
346
|
+
>>> round2_results = [dkg.keygen_round2(i+1, priv_states[i], round1_msgs) for i in range(3)]
|
|
347
|
+
>>> shares_for_others = [r[0] for r in round2_results]
|
|
348
|
+
>>> states_r2 = [r[1] for r in round2_results]
|
|
349
|
+
>>> # Collect shares for party 1
|
|
350
|
+
>>> received = {sender+1: shares_for_others[sender][1] for sender in range(3)}
|
|
351
|
+
>>> ks, complaint = dkg.keygen_round3(1, states_r2[0], received, round1_msgs)
|
|
352
|
+
>>> isinstance(ks, KeyShare)
|
|
353
|
+
True
|
|
354
|
+
>>> ks.party_id == 1
|
|
355
|
+
True
|
|
356
|
+
>>> complaint is None
|
|
357
|
+
True
|
|
358
|
+
"""
|
|
359
|
+
generator = private_state['generator']
|
|
360
|
+
|
|
361
|
+
# Build a mapping from party_id to round1 message
|
|
362
|
+
round1_by_party = {msg['party_id']: msg for msg in all_round1_msgs}
|
|
363
|
+
|
|
364
|
+
# Verify all received shares against commitments
|
|
365
|
+
for sender_id, share in received_shares.items():
|
|
366
|
+
commitments = round1_by_party[sender_id]['commitments']
|
|
367
|
+
if not self._verify_share_against_commitments(
|
|
368
|
+
sender_id, party_id, share, commitments, generator
|
|
369
|
+
):
|
|
370
|
+
# Generate complaint instead of raising ValueError
|
|
371
|
+
complaint = {
|
|
372
|
+
'accuser': party_id,
|
|
373
|
+
'accused': sender_id,
|
|
374
|
+
'share': share,
|
|
375
|
+
'commitments': commitments
|
|
376
|
+
}
|
|
377
|
+
return (None, complaint)
|
|
378
|
+
|
|
379
|
+
# Compute final share: x_j = sum_{i=1}^{n} f_i(j)
|
|
380
|
+
final_share = self.group.init(ZR, 0)
|
|
381
|
+
for sender_id, share in received_shares.items():
|
|
382
|
+
final_share = final_share + share
|
|
383
|
+
|
|
384
|
+
# Compute verification key: X_j = g^{x_j}
|
|
385
|
+
verification_key = generator ** final_share
|
|
386
|
+
|
|
387
|
+
# Compute public key: X = prod_{i=1}^{n} C_{i,0}
|
|
388
|
+
public_key = self.compute_public_key(
|
|
389
|
+
[round1_by_party[i]['commitments'] for i in range(1, self.n + 1)],
|
|
390
|
+
generator
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
key_share = KeyShare(
|
|
394
|
+
party_id=party_id,
|
|
395
|
+
private_share=final_share,
|
|
396
|
+
public_key=public_key,
|
|
397
|
+
verification_key=verification_key,
|
|
398
|
+
threshold=self.t,
|
|
399
|
+
num_parties=self.n
|
|
400
|
+
)
|
|
401
|
+
return (key_share, None)
|
|
402
|
+
|
|
403
|
+
def handle_complaints(self, party_id: PartyId, complaints: Dict[PartyId, Dict[str, Any]], all_round1_msgs: List[Dict[str, Any]]) -> Set[PartyId]:
|
|
404
|
+
"""
|
|
405
|
+
Process complaints and identify disqualified parties.
|
|
406
|
+
|
|
407
|
+
When parties report share verification failures via complaints, this
|
|
408
|
+
method verifies each complaint and determines which parties should be
|
|
409
|
+
disqualified from the protocol.
|
|
410
|
+
|
|
411
|
+
A complaint is valid if the accused party's share does not verify against
|
|
412
|
+
their public commitments. If a complaint is valid, the accused is
|
|
413
|
+
disqualified. If a complaint is invalid (the share actually verifies),
|
|
414
|
+
the accuser is making a false accusation and may be disqualified.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
party_id: This party's identifier (for context)
|
|
418
|
+
complaints: Dict mapping accuser party_id to complaint dict containing:
|
|
419
|
+
- 'accuser': ID of party making complaint
|
|
420
|
+
- 'accused': ID of party being accused
|
|
421
|
+
- 'share': The share that failed verification
|
|
422
|
+
- 'commitments': The commitments used for verification
|
|
423
|
+
all_round1_msgs: List of broadcast messages from all parties
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
Set of party IDs that should be disqualified
|
|
427
|
+
|
|
428
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
429
|
+
>>> group = ECGroup(secp256k1)
|
|
430
|
+
>>> dkg = DKLS23_DKG(group, threshold=2, num_parties=3)
|
|
431
|
+
>>> g = group.random(G)
|
|
432
|
+
>>> # No complaints case
|
|
433
|
+
>>> disqualified = dkg.handle_complaints(1, {}, [])
|
|
434
|
+
>>> len(disqualified) == 0
|
|
435
|
+
True
|
|
436
|
+
"""
|
|
437
|
+
if not complaints:
|
|
438
|
+
return set()
|
|
439
|
+
|
|
440
|
+
disqualified = set()
|
|
441
|
+
round1_by_party = {msg['party_id']: msg for msg in all_round1_msgs}
|
|
442
|
+
|
|
443
|
+
for accuser_id, complaint in complaints.items():
|
|
444
|
+
accused_id = complaint['accused']
|
|
445
|
+
share = complaint['share']
|
|
446
|
+
# Use the commitments from round1 messages (the public record)
|
|
447
|
+
# not from the complaint (which could be forged)
|
|
448
|
+
if accused_id in round1_by_party:
|
|
449
|
+
commitments = round1_by_party[accused_id]['commitments']
|
|
450
|
+
generator = None
|
|
451
|
+
# Find generator from any round1 message's first commitment context
|
|
452
|
+
for msg in all_round1_msgs:
|
|
453
|
+
if 'generator' in msg:
|
|
454
|
+
generator = msg['generator']
|
|
455
|
+
break
|
|
456
|
+
|
|
457
|
+
if generator is None:
|
|
458
|
+
# If generator not in messages, we can still verify using
|
|
459
|
+
# the structure of the commitments
|
|
460
|
+
# For now, trust the complaint's verification result
|
|
461
|
+
disqualified.add(accused_id)
|
|
462
|
+
else:
|
|
463
|
+
# Verify the complaint: is the share actually invalid?
|
|
464
|
+
is_valid_share = self._verify_share_against_commitments(
|
|
465
|
+
accused_id, accuser_id, share, commitments, generator
|
|
466
|
+
)
|
|
467
|
+
if not is_valid_share:
|
|
468
|
+
# Share is indeed invalid - accused party is malicious
|
|
469
|
+
disqualified.add(accused_id)
|
|
470
|
+
else:
|
|
471
|
+
# Share is valid - accuser made a false complaint
|
|
472
|
+
# This could indicate the accuser is malicious
|
|
473
|
+
disqualified.add(accuser_id)
|
|
474
|
+
else:
|
|
475
|
+
# Accused party not in round1 messages - they didn't participate
|
|
476
|
+
disqualified.add(accused_id)
|
|
477
|
+
|
|
478
|
+
return disqualified
|
|
479
|
+
|
|
480
|
+
def verify_broadcast_consistency(self, party_id: PartyId, all_round1_msgs: List[Dict[str, Any]], echo_msgs: Dict[PartyId, Dict[PartyId, bytes]]) -> bool:
|
|
481
|
+
"""
|
|
482
|
+
Verify echo broadcast consistency across all parties.
|
|
483
|
+
|
|
484
|
+
In a secure broadcast protocol, all parties must receive the same message
|
|
485
|
+
from each sender. This method implements echo broadcast verification by
|
|
486
|
+
comparing what each party claims to have received from each sender.
|
|
487
|
+
|
|
488
|
+
Without echo broadcast, a malicious party could send different commitments
|
|
489
|
+
to different recipients (equivocation attack).
|
|
490
|
+
|
|
491
|
+
Delegates to the EchoBroadcast toolbox for the actual verification logic.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
party_id: This party's identifier
|
|
495
|
+
all_round1_msgs: List of round 1 messages as received by this party
|
|
496
|
+
echo_msgs: Dict of {verifier_id: {sender_id: msg_hash}} where each
|
|
497
|
+
verifier reports the hash of what they received from each sender
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
True if all parties received consistent messages
|
|
501
|
+
|
|
502
|
+
Raises:
|
|
503
|
+
ValueError: If inconsistency detected, with details about which
|
|
504
|
+
sender sent different messages to different recipients
|
|
505
|
+
|
|
506
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
507
|
+
>>> group = ECGroup(secp256k1)
|
|
508
|
+
>>> dkg = DKLS23_DKG(group, threshold=2, num_parties=3)
|
|
509
|
+
>>> # Consistent case
|
|
510
|
+
>>> echo_msgs = {1: {2: b'hash1', 3: b'hash2'}, 2: {2: b'hash1', 3: b'hash2'}}
|
|
511
|
+
>>> dkg.verify_broadcast_consistency(1, [], echo_msgs)
|
|
512
|
+
True
|
|
513
|
+
"""
|
|
514
|
+
return self._broadcast.verify_consistency(echo_msgs)
|
|
515
|
+
|
|
516
|
+
def compute_public_key(self, all_commitments: List[List[GElement]], generator: GElement) -> GElement:
|
|
517
|
+
"""
|
|
518
|
+
Compute the combined public key from all parties' commitments
|
|
519
|
+
|
|
520
|
+
The public key is X = prod_{i=1}^{n} C_{i,0} = g^{sum s_i}
|
|
521
|
+
where C_{i,0} = g^{s_i} is the first commitment from party i.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
all_commitments: List of commitment lists from all parties
|
|
525
|
+
generator: Generator point g (unused but kept for API consistency)
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
The combined public key as a group element
|
|
529
|
+
|
|
530
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
531
|
+
>>> group = ECGroup(secp256k1)
|
|
532
|
+
>>> dkg = DKLS23_DKG(group, threshold=2, num_parties=3)
|
|
533
|
+
>>> g = group.random(G)
|
|
534
|
+
>>> states = [dkg.keygen_round1(i+1, g) for i in range(3)]
|
|
535
|
+
>>> all_comms = [s[0]['commitments'] for s in states]
|
|
536
|
+
>>> pk = dkg.compute_public_key(all_comms, g)
|
|
537
|
+
>>> # Public key should be product of all g^{s_i}
|
|
538
|
+
>>> secrets = [s[1]['secret'] for s in states]
|
|
539
|
+
>>> expected = g ** (secrets[0] + secrets[1] + secrets[2])
|
|
540
|
+
>>> pk == expected
|
|
541
|
+
True
|
|
542
|
+
"""
|
|
543
|
+
if not all_commitments:
|
|
544
|
+
raise ValueError("Need at least one commitment list")
|
|
545
|
+
|
|
546
|
+
# Public key = product of all first commitments (C_{i,0} = g^{s_i})
|
|
547
|
+
public_key = all_commitments[0][0]
|
|
548
|
+
for i in range(1, len(all_commitments)):
|
|
549
|
+
public_key = public_key * all_commitments[i][0]
|
|
550
|
+
|
|
551
|
+
return public_key
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
if __name__ == "__main__":
|
|
555
|
+
import doctest
|
|
556
|
+
doctest.testmod()
|