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.
Files changed (323) hide show
  1. charm/__init__.py +5 -0
  2. charm/adapters/__init__.py +0 -0
  3. charm/adapters/abenc_adapt_hybrid.py +90 -0
  4. charm/adapters/dabenc_adapt_hybrid.py +145 -0
  5. charm/adapters/ibenc_adapt_hybrid.py +72 -0
  6. charm/adapters/ibenc_adapt_identityhash.py +80 -0
  7. charm/adapters/kpabenc_adapt_hybrid.py +91 -0
  8. charm/adapters/pkenc_adapt_bchk05.py +121 -0
  9. charm/adapters/pkenc_adapt_chk04.py +91 -0
  10. charm/adapters/pkenc_adapt_hybrid.py +98 -0
  11. charm/adapters/pksig_adapt_naor01.py +89 -0
  12. charm/config.py +7 -0
  13. charm/core/__init__.py +0 -0
  14. charm/core/benchmark/benchmark_util.c +353 -0
  15. charm/core/benchmark/benchmark_util.h +61 -0
  16. charm/core/benchmark/benchmarkmodule.c +476 -0
  17. charm/core/benchmark/benchmarkmodule.h +162 -0
  18. charm/core/benchmark.cpython-313-darwin.so +0 -0
  19. charm/core/crypto/AES/AES.c +1464 -0
  20. charm/core/crypto/AES.cpython-313-darwin.so +0 -0
  21. charm/core/crypto/DES/DES.c +113 -0
  22. charm/core/crypto/DES.cpython-313-darwin.so +0 -0
  23. charm/core/crypto/DES3/DES3.c +26 -0
  24. charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
  25. charm/core/crypto/__init__.py +0 -0
  26. charm/core/crypto/cryptobase/XOR.c +80 -0
  27. charm/core/crypto/cryptobase/_counter.c +496 -0
  28. charm/core/crypto/cryptobase/_counter.h +54 -0
  29. charm/core/crypto/cryptobase/block_template.c +900 -0
  30. charm/core/crypto/cryptobase/block_template.h +69 -0
  31. charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
  32. charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
  33. charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
  34. charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
  35. charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
  36. charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
  37. charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
  38. charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
  39. charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
  40. charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
  41. charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
  42. charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
  43. charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
  44. charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
  45. charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
  46. charm/core/crypto/cryptobase/stream_template.c +271 -0
  47. charm/core/crypto/cryptobase/strxor.c +229 -0
  48. charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
  49. charm/core/engine/__init__.py +5 -0
  50. charm/core/engine/protocol.py +293 -0
  51. charm/core/engine/util.py +174 -0
  52. charm/core/math/__init__.py +0 -0
  53. charm/core/math/elliptic_curve/ecmodule.c +1986 -0
  54. charm/core/math/elliptic_curve/ecmodule.h +230 -0
  55. charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
  56. charm/core/math/elliptic_curve.pyi +63 -0
  57. charm/core/math/integer/integermodule.c +2539 -0
  58. charm/core/math/integer/integermodule.h +145 -0
  59. charm/core/math/integer.cpython-313-darwin.so +0 -0
  60. charm/core/math/integer.pyi +76 -0
  61. charm/core/math/pairing/miracl/miracl_config.h +37 -0
  62. charm/core/math/pairing/miracl/miracl_interface.h +118 -0
  63. charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
  64. charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
  65. charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
  66. charm/core/math/pairing/pairingmodule.c +2230 -0
  67. charm/core/math/pairing/pairingmodule.h +241 -0
  68. charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
  69. charm/core/math/pairing/relic/pairingmodule3.h +233 -0
  70. charm/core/math/pairing/relic/relic_interface.c +1337 -0
  71. charm/core/math/pairing/relic/relic_interface.h +217 -0
  72. charm/core/math/pairing/relic/test_relic.c +171 -0
  73. charm/core/math/pairing.cpython-313-darwin.so +0 -0
  74. charm/core/math/pairing.pyi +69 -0
  75. charm/core/utilities/base64.c +248 -0
  76. charm/core/utilities/base64.h +15 -0
  77. charm/schemes/__init__.py +0 -0
  78. charm/schemes/abenc/__init__.py +0 -0
  79. charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
  80. charm/schemes/abenc/abenc_bsw07.py +146 -0
  81. charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
  82. charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
  83. charm/schemes/abenc/abenc_lsw08.py +159 -0
  84. charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
  85. charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
  86. charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
  87. charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
  88. charm/schemes/abenc/abenc_waters09.py +144 -0
  89. charm/schemes/abenc/abenc_yct14.py +208 -0
  90. charm/schemes/abenc/abenc_yllc15.py +178 -0
  91. charm/schemes/abenc/ac17.py +248 -0
  92. charm/schemes/abenc/bsw07.py +141 -0
  93. charm/schemes/abenc/cgw15.py +277 -0
  94. charm/schemes/abenc/dabe_aw11.py +204 -0
  95. charm/schemes/abenc/dfa_fe12.py +144 -0
  96. charm/schemes/abenc/pk_hve08.py +179 -0
  97. charm/schemes/abenc/waters11.py +143 -0
  98. charm/schemes/aggrsign_MuSig.py +150 -0
  99. charm/schemes/aggrsign_bls.py +267 -0
  100. charm/schemes/blindsig_ps16.py +654 -0
  101. charm/schemes/chamhash_adm05.py +113 -0
  102. charm/schemes/chamhash_rsa_hw09.py +100 -0
  103. charm/schemes/commit/__init__.py +0 -0
  104. charm/schemes/commit/commit_gs08.py +77 -0
  105. charm/schemes/commit/commit_pedersen92.py +53 -0
  106. charm/schemes/encap_bchk05.py +62 -0
  107. charm/schemes/grpsig/__init__.py +0 -0
  108. charm/schemes/grpsig/groupsig_bgls04.py +114 -0
  109. charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
  110. charm/schemes/hibenc/__init__.py +0 -0
  111. charm/schemes/hibenc/hibenc_bb04.py +105 -0
  112. charm/schemes/hibenc/hibenc_lew11.py +193 -0
  113. charm/schemes/ibenc/__init__.py +0 -0
  114. charm/schemes/ibenc/clpkc_rp03.py +119 -0
  115. charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
  116. charm/schemes/ibenc/ibenc_bb03.py +94 -0
  117. charm/schemes/ibenc/ibenc_bf01.py +121 -0
  118. charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
  119. charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
  120. charm/schemes/ibenc/ibenc_lsw08.py +120 -0
  121. charm/schemes/ibenc/ibenc_sw05.py +238 -0
  122. charm/schemes/ibenc/ibenc_waters05.py +144 -0
  123. charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
  124. charm/schemes/ibenc/ibenc_waters09.py +107 -0
  125. charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
  126. charm/schemes/joye_scheme.py +106 -0
  127. charm/schemes/lem_scheme.py +207 -0
  128. charm/schemes/pk_fre_ccv11.py +107 -0
  129. charm/schemes/pk_vrf.py +127 -0
  130. charm/schemes/pkenc/__init__.py +0 -0
  131. charm/schemes/pkenc/pkenc_cs98.py +108 -0
  132. charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
  133. charm/schemes/pkenc/pkenc_gm82.py +98 -0
  134. charm/schemes/pkenc/pkenc_paillier99.py +118 -0
  135. charm/schemes/pkenc/pkenc_rabin.py +254 -0
  136. charm/schemes/pkenc/pkenc_rsa.py +186 -0
  137. charm/schemes/pksig/__init__.py +0 -0
  138. charm/schemes/pksig/pksig_CW13_z.py +135 -0
  139. charm/schemes/pksig/pksig_bls04.py +87 -0
  140. charm/schemes/pksig/pksig_boyen.py +156 -0
  141. charm/schemes/pksig/pksig_chch.py +97 -0
  142. charm/schemes/pksig/pksig_chp.py +70 -0
  143. charm/schemes/pksig/pksig_cl03.py +150 -0
  144. charm/schemes/pksig/pksig_cl04.py +87 -0
  145. charm/schemes/pksig/pksig_cllww12_z.py +142 -0
  146. charm/schemes/pksig/pksig_cyh.py +132 -0
  147. charm/schemes/pksig/pksig_dsa.py +76 -0
  148. charm/schemes/pksig/pksig_ecdsa.py +71 -0
  149. charm/schemes/pksig/pksig_hess.py +104 -0
  150. charm/schemes/pksig/pksig_hw.py +110 -0
  151. charm/schemes/pksig/pksig_lamport.py +63 -0
  152. charm/schemes/pksig/pksig_ps01.py +135 -0
  153. charm/schemes/pksig/pksig_ps02.py +124 -0
  154. charm/schemes/pksig/pksig_ps03.py +119 -0
  155. charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
  156. charm/schemes/pksig/pksig_schnorr91.py +77 -0
  157. charm/schemes/pksig/pksig_waters.py +115 -0
  158. charm/schemes/pksig/pksig_waters05.py +121 -0
  159. charm/schemes/pksig/pksig_waters09.py +121 -0
  160. charm/schemes/pre_mg07.py +150 -0
  161. charm/schemes/prenc/pre_afgh06.py +126 -0
  162. charm/schemes/prenc/pre_bbs98.py +123 -0
  163. charm/schemes/prenc/pre_nal16.py +216 -0
  164. charm/schemes/protocol_a01.py +272 -0
  165. charm/schemes/protocol_ao00.py +215 -0
  166. charm/schemes/protocol_cns07.py +274 -0
  167. charm/schemes/protocol_schnorr91.py +125 -0
  168. charm/schemes/sigma1.py +64 -0
  169. charm/schemes/sigma2.py +129 -0
  170. charm/schemes/sigma3.py +126 -0
  171. charm/schemes/threshold/__init__.py +59 -0
  172. charm/schemes/threshold/dkls23_dkg.py +556 -0
  173. charm/schemes/threshold/dkls23_presign.py +1089 -0
  174. charm/schemes/threshold/dkls23_sign.py +761 -0
  175. charm/schemes/threshold/xrpl_wallet.py +967 -0
  176. charm/test/__init__.py +0 -0
  177. charm/test/adapters/__init__.py +0 -0
  178. charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
  179. charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
  180. charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
  181. charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
  182. charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
  183. charm/test/benchmark/abenc_yllc15_bench.py +92 -0
  184. charm/test/benchmark/benchmark_test.py +148 -0
  185. charm/test/benchmark_threshold.py +260 -0
  186. charm/test/conftest.py +38 -0
  187. charm/test/fuzz/__init__.py +1 -0
  188. charm/test/fuzz/conftest.py +5 -0
  189. charm/test/fuzz/fuzz_policy_parser.py +76 -0
  190. charm/test/fuzz/fuzz_serialization.py +83 -0
  191. charm/test/schemes/__init__.py +0 -0
  192. charm/test/schemes/abenc/__init__.py +0 -0
  193. charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
  194. charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
  195. charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
  196. charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
  197. charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
  198. charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
  199. charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
  200. charm/test/schemes/chamhash_adm05_test.py +31 -0
  201. charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
  202. charm/test/schemes/commit/__init__.py +0 -0
  203. charm/test/schemes/commit/commit_gs08_test.py +24 -0
  204. charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
  205. charm/test/schemes/dabe_aw11_test.py +45 -0
  206. charm/test/schemes/encap_bchk05_test.py +21 -0
  207. charm/test/schemes/grpsig/__init__.py +0 -0
  208. charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
  209. charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
  210. charm/test/schemes/hibenc/__init__.py +0 -0
  211. charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
  212. charm/test/schemes/ibenc/__init__.py +0 -0
  213. charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
  214. charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
  215. charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
  216. charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
  217. charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
  218. charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
  219. charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
  220. charm/test/schemes/pk_vrf_test.py +29 -0
  221. charm/test/schemes/pkenc/__init__.py +0 -0
  222. charm/test/schemes/pkenc_test.py +255 -0
  223. charm/test/schemes/pksig/__init__.py +0 -0
  224. charm/test/schemes/pksig_test.py +376 -0
  225. charm/test/schemes/rsa_alg_test.py +340 -0
  226. charm/test/schemes/threshold_test.py +1792 -0
  227. charm/test/serialize/__init__.py +0 -0
  228. charm/test/serialize/serialize_test.py +40 -0
  229. charm/test/toolbox/__init__.py +0 -0
  230. charm/test/toolbox/conversion_test.py +30 -0
  231. charm/test/toolbox/ecgroup_test.py +53 -0
  232. charm/test/toolbox/integer_arithmetic_test.py +441 -0
  233. charm/test/toolbox/paddingschemes_test.py +238 -0
  234. charm/test/toolbox/policy_parser_stress_test.py +969 -0
  235. charm/test/toolbox/secretshare_test.py +28 -0
  236. charm/test/toolbox/symcrypto_test.py +108 -0
  237. charm/test/toolbox/test_policy_expression.py +16 -0
  238. charm/test/vectors/__init__.py +1 -0
  239. charm/test/vectors/test_bls_vectors.py +289 -0
  240. charm/test/vectors/test_pedersen_vectors.py +315 -0
  241. charm/test/vectors/test_schnorr_vectors.py +368 -0
  242. charm/test/zkp_compiler/__init__.py +9 -0
  243. charm/test/zkp_compiler/benchmark_zkp.py +258 -0
  244. charm/test/zkp_compiler/test_and_proof.py +240 -0
  245. charm/test/zkp_compiler/test_batch_verify.py +248 -0
  246. charm/test/zkp_compiler/test_dleq_proof.py +264 -0
  247. charm/test/zkp_compiler/test_or_proof.py +231 -0
  248. charm/test/zkp_compiler/test_proof_serialization.py +121 -0
  249. charm/test/zkp_compiler/test_range_proof.py +241 -0
  250. charm/test/zkp_compiler/test_representation_proof.py +325 -0
  251. charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
  252. charm/test/zkp_compiler/test_thread_safety.py +169 -0
  253. charm/test/zkp_compiler/test_zkp_parser.py +139 -0
  254. charm/toolbox/ABEnc.py +26 -0
  255. charm/toolbox/ABEncMultiAuth.py +66 -0
  256. charm/toolbox/ABEnumeric.py +800 -0
  257. charm/toolbox/Commit.py +24 -0
  258. charm/toolbox/DFA.py +89 -0
  259. charm/toolbox/FSA.py +1254 -0
  260. charm/toolbox/Hash.py +39 -0
  261. charm/toolbox/IBEnc.py +62 -0
  262. charm/toolbox/IBSig.py +64 -0
  263. charm/toolbox/PKEnc.py +66 -0
  264. charm/toolbox/PKSig.py +56 -0
  265. charm/toolbox/PREnc.py +32 -0
  266. charm/toolbox/ZKProof.py +289 -0
  267. charm/toolbox/__init__.py +0 -0
  268. charm/toolbox/bitstring.py +49 -0
  269. charm/toolbox/broadcast.py +220 -0
  270. charm/toolbox/conversion.py +100 -0
  271. charm/toolbox/eccurve.py +149 -0
  272. charm/toolbox/ecgroup.py +143 -0
  273. charm/toolbox/enum.py +60 -0
  274. charm/toolbox/hash_module.py +91 -0
  275. charm/toolbox/integergroup.py +323 -0
  276. charm/toolbox/iterate.py +22 -0
  277. charm/toolbox/matrixops.py +76 -0
  278. charm/toolbox/mpc_utils.py +296 -0
  279. charm/toolbox/msp.py +175 -0
  280. charm/toolbox/mta.py +985 -0
  281. charm/toolbox/node.py +120 -0
  282. charm/toolbox/ot/__init__.py +22 -0
  283. charm/toolbox/ot/base_ot.py +374 -0
  284. charm/toolbox/ot/dpf.py +642 -0
  285. charm/toolbox/ot/mpfss.py +228 -0
  286. charm/toolbox/ot/ot_extension.py +589 -0
  287. charm/toolbox/ot/silent_ot.py +378 -0
  288. charm/toolbox/paddingschemes.py +423 -0
  289. charm/toolbox/paddingschemes_test.py +238 -0
  290. charm/toolbox/pairingcurves.py +85 -0
  291. charm/toolbox/pairinggroup.py +186 -0
  292. charm/toolbox/policy_expression_spec.py +70 -0
  293. charm/toolbox/policytree.py +189 -0
  294. charm/toolbox/reCompiler.py +346 -0
  295. charm/toolbox/redundancyschemes.py +65 -0
  296. charm/toolbox/schemebase.py +188 -0
  297. charm/toolbox/secretshare.py +104 -0
  298. charm/toolbox/secretutil.py +174 -0
  299. charm/toolbox/securerandom.py +73 -0
  300. charm/toolbox/sigmaprotocol.py +46 -0
  301. charm/toolbox/specialprimes.py +45 -0
  302. charm/toolbox/symcrypto.py +279 -0
  303. charm/toolbox/threshold_sharing.py +553 -0
  304. charm/toolbox/xmlserialize.py +94 -0
  305. charm/toolbox/zknode.py +105 -0
  306. charm/zkp_compiler/__init__.py +89 -0
  307. charm/zkp_compiler/and_proof.py +460 -0
  308. charm/zkp_compiler/batch_verify.py +324 -0
  309. charm/zkp_compiler/dleq_proof.py +423 -0
  310. charm/zkp_compiler/or_proof.py +305 -0
  311. charm/zkp_compiler/range_proof.py +417 -0
  312. charm/zkp_compiler/representation_proof.py +466 -0
  313. charm/zkp_compiler/schnorr_proof.py +273 -0
  314. charm/zkp_compiler/thread_safe.py +150 -0
  315. charm/zkp_compiler/zk_demo.py +489 -0
  316. charm/zkp_compiler/zkp_factory.py +330 -0
  317. charm/zkp_compiler/zkp_generator.py +370 -0
  318. charm/zkp_compiler/zkparser.py +269 -0
  319. charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
  320. charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
  321. charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
  322. charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
  323. 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()