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,761 @@
1
+ '''
2
+ DKLS23 Signing Protocol and Main Class for 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: threshold signing
10
+ * setting: Elliptic Curve DDH-hard group
11
+ * assumption: DDH + OT security
12
+
13
+ This module implements the signing phase of the DKLS23 threshold ECDSA
14
+ protocol, combining presignatures with messages to produce standard
15
+ ECDSA signatures that can be verified by any ECDSA implementation.
16
+
17
+ Protocol Overview:
18
+ 1. Sign Round: Each party i computes signature share:
19
+ s_i = k_i * H(m) + r * χ_i (where χ_i is their share of k*x)
20
+
21
+ 2. Combine: Use Lagrange coefficients to combine shares:
22
+ s = ∑ λ_i * s_i mod q
23
+
24
+ 3. Normalize: If s > q/2, set s = q - s (low-s normalization for malleability)
25
+
26
+ :Authors: Elton de Souza
27
+ :Date: 01/2026
28
+ '''
29
+
30
+ from typing import Dict, List, Tuple, Optional, Any, Union
31
+
32
+ from charm.toolbox.ecgroup import ECGroup, ZR, G
33
+ from charm.toolbox.eccurve import secp256k1
34
+ from charm.toolbox.PKSig import PKSig
35
+ from charm.toolbox.threshold_sharing import ThresholdSharing
36
+ from charm.toolbox.Hash import Hash
37
+ from charm.schemes.threshold.dkls23_dkg import DKLS23_DKG, KeyShare
38
+ from charm.schemes.threshold.dkls23_presign import DKLS23_Presign, Presignature
39
+
40
+ # Type aliases for charm-crypto types
41
+ ZRElement = Any # Scalar field element
42
+ GElement = Any # Group/curve point element
43
+ ECGroupType = Any # ECGroup instance
44
+ PartyId = int
45
+
46
+
47
+ class ThresholdSignature:
48
+ """
49
+ Represents a threshold ECDSA signature.
50
+
51
+ Contains the (r, s) values that form a standard ECDSA signature.
52
+
53
+ >>> from charm.toolbox.eccurve import secp256k1
54
+ >>> group = ECGroup(secp256k1)
55
+ >>> r = group.random(ZR)
56
+ >>> s = group.random(ZR)
57
+ >>> sig = ThresholdSignature(r, s)
58
+ >>> sig.r == r and sig.s == s
59
+ True
60
+ """
61
+
62
+ def __init__(self, r: ZRElement, s: ZRElement) -> None:
63
+ """
64
+ Initialize a threshold signature.
65
+
66
+ Args:
67
+ r: The r component (x-coordinate of R point mod q)
68
+ s: The s component (signature value)
69
+ """
70
+ self.r = r
71
+ self.s = s
72
+
73
+ def __repr__(self) -> str:
74
+ return f"ThresholdSignature(r=..., s=...)"
75
+
76
+ def __eq__(self, other: object) -> bool:
77
+ if isinstance(other, ThresholdSignature):
78
+ return self.r == other.r and self.s == other.s
79
+ return False
80
+
81
+ def to_der(self) -> bytes:
82
+ """
83
+ Convert to DER encoding for external verification.
84
+
85
+ Returns:
86
+ DER-encoded signature bytes.
87
+
88
+ Note: This requires the r and s values to be convertible to integers.
89
+ """
90
+ def int_to_der_integer(val):
91
+ """Convert integer to DER INTEGER encoding."""
92
+ if hasattr(val, '__int__'):
93
+ val = int(val)
94
+ val_bytes = val.to_bytes((val.bit_length() + 7) // 8, 'big')
95
+ # Add leading zero if high bit is set (for positive representation)
96
+ if val_bytes[0] & 0x80:
97
+ val_bytes = b'\x00' + val_bytes
98
+ return bytes([0x02, len(val_bytes)]) + val_bytes
99
+
100
+ r_der = int_to_der_integer(self.r)
101
+ s_der = int_to_der_integer(self.s)
102
+ sequence = r_der + s_der
103
+ return bytes([0x30, len(sequence)]) + sequence
104
+
105
+
106
+ class DKLS23_Sign:
107
+ """
108
+ DKLS23 Signing Protocol
109
+
110
+ Combines presignatures with message to produce threshold ECDSA signature.
111
+
112
+ >>> from charm.toolbox.eccurve import secp256k1
113
+ >>> group = ECGroup(secp256k1)
114
+ >>> signer = DKLS23_Sign(group)
115
+ >>> # Simulate presignatures for a 2-of-3 setup
116
+ >>> g = group.random(G)
117
+ >>> k = group.random(ZR) # Combined nonce
118
+ >>> x = group.random(ZR) # Combined private key
119
+ >>> chi = k * x # k*x product
120
+ >>> R = g ** k
121
+ >>> r = group.zr(R)
122
+ >>> # Create share components (simplified for testing)
123
+ >>> ts = ThresholdSharing(group)
124
+ >>> k_shares = ts.share(k, 2, 3)
125
+ >>> chi_shares = ts.share(chi, 2, 3)
126
+ >>> presigs = {}
127
+ >>> for pid in [1, 2]:
128
+ ... presigs[pid] = Presignature(pid, R, r, k_shares[pid], chi_shares[pid], [1, 2])
129
+ >>> # Create key share (simplified)
130
+ >>> key_share = KeyShare(1, ts.share(x, 2, 3)[1], g ** x, g ** ts.share(x, 2, 3)[1], 2, 3)
131
+ >>> message = b"test message"
132
+ >>> sig_share, proof = signer.sign_round1(1, presigs[1], key_share, message, [1, 2])
133
+ >>> sig_share is not None
134
+ True
135
+ """
136
+
137
+ def __init__(self, groupObj: ECGroupType) -> None:
138
+ """
139
+ Initialize the signing protocol.
140
+
141
+ Args:
142
+ groupObj: An ECGroup instance (e.g., ECGroup(secp256k1))
143
+
144
+ Raises:
145
+ ValueError: If groupObj is None
146
+ """
147
+ if groupObj is None:
148
+ raise ValueError("groupObj cannot be None")
149
+ self.group = groupObj
150
+ self.order = int(groupObj.order()) # Convert to int for modular arithmetic
151
+ self._sharing = ThresholdSharing(groupObj)
152
+
153
+ def _hash_message(self, message: bytes) -> ZRElement:
154
+ """
155
+ Hash message to a scalar using group.hash() with domain separation.
156
+
157
+ Uses group.hash() for proper domain separation and consistent
158
+ hash-to-field element conversion.
159
+
160
+ Args:
161
+ message: Message bytes to hash
162
+
163
+ Returns:
164
+ Hash as a ZR element
165
+ """
166
+ if isinstance(message, str):
167
+ message = message.encode('utf-8')
168
+ return self.group.hash((b"ECDSA_MSG:", message), target_type=ZR)
169
+
170
+ def sign_round1(self, party_id: PartyId, presignature: Presignature, key_share: KeyShare, message: bytes, participants: List[PartyId], delta_inv: ZRElement, prehashed: bool = False) -> Tuple[ZRElement, Dict[str, Any]]:
171
+ """
172
+ Generate signature share for message.
173
+
174
+ Computes s_i = delta^{-1} * (e * gamma_i + r * sigma_i)
175
+ where sigma_i is stored in chi_i (share of gamma*x)
176
+
177
+ Args:
178
+ party_id: This party's identifier
179
+ presignature: Presignature object for this party
180
+ key_share: KeyShare object for this party
181
+ message: Message bytes to sign
182
+ participants: List of participating party IDs
183
+ delta_inv: Inverse of delta = sum(delta_i), computed externally (required)
184
+ prehashed: If True, message is already a 32-byte hash (no additional hashing).
185
+ Use this for protocols like XRPL that provide their own signing hash.
186
+
187
+ Returns:
188
+ Tuple of (signature_share, proof)
189
+ - signature_share: Party's contribution to s
190
+ - proof: Placeholder for ZK proof (dict)
191
+
192
+ Raises:
193
+ ValueError: If delta_inv is None
194
+
195
+ >>> from charm.toolbox.eccurve import secp256k1
196
+ >>> group = ECGroup(secp256k1)
197
+ >>> signer = DKLS23_Sign(group)
198
+ >>> g = group.random(G)
199
+ >>> k_i = group.random(ZR)
200
+ >>> gamma_i = group.random(ZR)
201
+ >>> sigma_i = group.random(ZR)
202
+ >>> delta_i = k_i * gamma_i
203
+ >>> R = g ** group.random(ZR)
204
+ >>> r = group.zr(R)
205
+ >>> ps = Presignature(1, R, r, k_i, sigma_i, [1, 2], gamma_i, delta_i)
206
+ >>> ks = KeyShare(1, group.random(ZR), g, g, 2, 3)
207
+ >>> delta_inv = delta_i ** -1 # Simplified for test
208
+ >>> share, proof = signer.sign_round1(1, ps, ks, b"test", [1, 2], delta_inv)
209
+ >>> share is not None
210
+ True
211
+ """
212
+ # Hash the message: e = H(m)
213
+ if prehashed:
214
+ # Message is already a 32-byte hash
215
+ if len(message) != 32:
216
+ raise ValueError("prehashed message must be exactly 32 bytes")
217
+ h_int = int.from_bytes(message, 'big') % self.order
218
+ e = self.group.init(ZR, h_int)
219
+ else:
220
+ e = self._hash_message(message)
221
+
222
+ # Get presignature components
223
+ gamma_i = presignature.gamma_i
224
+ sigma_i = presignature.chi_i # sigma_i = share of gamma*x
225
+ r = presignature.r
226
+
227
+ # Validate required delta_inv parameter
228
+ if delta_inv is None:
229
+ raise ValueError("delta_inv is required for valid signature generation")
230
+
231
+ # DKLS23 formula: s_i = delta^{-1} * (e * gamma_i + r * sigma_i)
232
+ s_i = delta_inv * ((e * gamma_i) + (r * sigma_i))
233
+
234
+ # Proof placeholder (in full implementation, would include ZK proof)
235
+ proof = {
236
+ 'party_id': party_id,
237
+ 'R': presignature.R
238
+ }
239
+
240
+ return s_i, proof
241
+
242
+ def verify_signature_share(self, party_id: PartyId, share: ZRElement, proof: Dict[str, Any], presignature: Presignature, message: bytes) -> bool:
243
+ """
244
+ Verify a signature share is well-formed.
245
+
246
+ Args:
247
+ party_id: The party that generated the share
248
+ share: The signature share (s_i value)
249
+ proof: The proof from sign_round1
250
+ presignature: The presignature used
251
+ message: The message being signed
252
+
253
+ Returns:
254
+ bool: True if share appears valid, False otherwise
255
+ """
256
+ # Basic validation: check share is a valid ZR element
257
+ if share is None:
258
+ return False
259
+
260
+ # Check share is in valid range (0, order)
261
+ try:
262
+ share_int = int(share)
263
+ if share_int <= 0 or share_int >= self.order:
264
+ return False
265
+ except:
266
+ return False
267
+
268
+ # Verify proof contains expected party_id
269
+ if proof.get('party_id') != party_id:
270
+ return False
271
+
272
+ return True
273
+
274
+ def combine_signatures(self, signature_shares: Dict[PartyId, ZRElement], presignature: Presignature, participants: List[PartyId], proofs: Optional[Dict[PartyId, Dict[str, Any]]] = None, message: Optional[bytes] = None) -> 'ThresholdSignature':
275
+ """
276
+ Combine signature shares into final signature.
277
+
278
+ In DKLS23, signature shares are additive (not polynomial shares),
279
+ so we use simple sum instead of Lagrange interpolation.
280
+
281
+ Args:
282
+ signature_shares: Dict mapping party_id to signature share
283
+ presignature: Any party's presignature (for r value)
284
+ participants: List of participating party IDs
285
+ proofs: Optional dict mapping party_id to proof from sign_round1
286
+ message: Optional message bytes (required if proofs provided)
287
+
288
+ Returns:
289
+ ThresholdSignature object with (r, s)
290
+
291
+ Raises:
292
+ ValueError: If a signature share fails verification
293
+
294
+ >>> from charm.toolbox.eccurve import secp256k1
295
+ >>> group = ECGroup(secp256k1)
296
+ >>> signer = DKLS23_Sign(group)
297
+ >>> # Simulate signature shares
298
+ >>> s1 = group.random(ZR)
299
+ >>> s2 = group.random(ZR)
300
+ >>> shares = {1: s1, 2: s2}
301
+ >>> g = group.random(G)
302
+ >>> R = g ** group.random(ZR)
303
+ >>> r = group.zr(R)
304
+ >>> ps = Presignature(1, R, r, group.random(ZR), group.random(ZR), [1, 2])
305
+ >>> sig = signer.combine_signatures(shares, ps, [1, 2])
306
+ >>> isinstance(sig, ThresholdSignature)
307
+ True
308
+ """
309
+ r = presignature.r
310
+
311
+ # DKLS23: Signature shares are additive, so just sum them
312
+ # s = sum(s_i) where s_i = delta^{-1} * (e * gamma_i + r * sigma_i)
313
+ # The delta^{-1} factor ensures correct reconstruction
314
+ s = self.group.init(ZR, 0)
315
+
316
+ for party_id in participants:
317
+ if party_id in signature_shares:
318
+ share = signature_shares[party_id]
319
+
320
+ # Verify share if proofs provided
321
+ if proofs is not None and message is not None:
322
+ proof = proofs.get(party_id, {})
323
+ if not self.verify_signature_share(party_id, share, proof, presignature, message):
324
+ raise ValueError(f"Invalid signature share from party {party_id}")
325
+
326
+ s = s + share
327
+
328
+ # Low-s normalization: if s > q/2, set s = q - s
329
+ # This prevents signature malleability
330
+ s = self._normalize_s(s)
331
+
332
+ return ThresholdSignature(r, s)
333
+
334
+ def _normalize_s(self, s: ZRElement) -> ZRElement:
335
+ """
336
+ Normalize s to low-s form (BIP-62 / BIP-146 compliant).
337
+
338
+ If s > order/2, return order - s.
339
+ This prevents signature malleability where (r, s) and (r, order-s)
340
+ are both valid signatures for the same message.
341
+
342
+ Args:
343
+ s: The s value to normalize (ZR element)
344
+
345
+ Returns:
346
+ Normalized s value as ZR element
347
+ """
348
+ # Convert to integer for comparison
349
+ s_int = int(s) % self.order
350
+ half_order = self.order // 2
351
+
352
+ if s_int > half_order:
353
+ # s is in high range, normalize to low-s form
354
+ normalized_s_int = self.order - s_int
355
+ return self.group.init(ZR, normalized_s_int)
356
+
357
+ return s
358
+
359
+ def verify(self, public_key: GElement, signature: Union['ThresholdSignature', Tuple[ZRElement, ZRElement]], message: bytes, generator: GElement) -> bool:
360
+ """
361
+ Verify ECDSA signature (standard verification).
362
+
363
+ This verifies standard ECDSA signatures that can be checked
364
+ by any ECDSA implementation.
365
+
366
+ Args:
367
+ public_key: The combined public key (EC point)
368
+ signature: ThresholdSignature or tuple (r, s)
369
+ message: The message that was signed
370
+ generator: Generator point g
371
+
372
+ Returns:
373
+ True if valid, False otherwise
374
+
375
+ >>> from charm.toolbox.eccurve import secp256k1
376
+ >>> group = ECGroup(secp256k1)
377
+ >>> signer = DKLS23_Sign(group)
378
+ >>> g = group.random(G)
379
+ >>> # Create valid signature manually
380
+ >>> x = group.random(ZR) # private key
381
+ >>> pk = g ** x # public key
382
+ >>> k = group.random(ZR) # nonce
383
+ >>> R = g ** k
384
+ >>> r = group.zr(R)
385
+ >>> message = b"test message"
386
+ >>> e = signer._hash_message(message)
387
+ >>> s = (e + r * x) * (k ** -1) # Standard ECDSA: s = k^{-1}(e + rx)
388
+ >>> sig = ThresholdSignature(r, s)
389
+ >>> signer.verify(pk, sig, message, g)
390
+ True
391
+ """
392
+ if isinstance(signature, tuple):
393
+ r, s = signature
394
+ else:
395
+ r, s = signature.r, signature.s
396
+
397
+ # Hash the message
398
+ e = self._hash_message(message)
399
+
400
+ # Compute s^{-1}
401
+ s_inv = s ** -1
402
+
403
+ # Compute u1 = e * s^{-1} and u2 = r * s^{-1}
404
+ u1 = e * s_inv
405
+ u2 = r * s_inv
406
+
407
+ # Compute R' = u1 * G + u2 * public_key
408
+ R_prime = (generator ** u1) * (public_key ** u2)
409
+
410
+ # Get x-coordinate of R'
411
+ r_prime = self.group.zr(R_prime)
412
+
413
+ # Verify r == r'
414
+ return r == r_prime
415
+
416
+
417
+ class DKLS23(PKSig):
418
+ """
419
+ DKLS23 Threshold ECDSA - Complete Implementation
420
+
421
+ Implements t-of-n threshold ECDSA signatures using the DKLS23 protocol.
422
+ Produces standard ECDSA signatures verifiable by any implementation.
423
+
424
+ Curve Agnostic
425
+ --------------
426
+ This implementation supports any elliptic curve group that is DDH-hard
427
+ (Decisional Diffie-Hellman). The curve is specified via the groupObj
428
+ parameter - examples include secp256k1, prime256v1 (P-256/secp256r1),
429
+ secp384r1, secp521r1, etc.
430
+
431
+ Example with secp256k1:
432
+ >>> from charm.toolbox.eccurve import secp256k1
433
+ >>> group = ECGroup(secp256k1)
434
+ >>> dkls = DKLS23(group, threshold=2, num_parties=3, party_id=1)
435
+
436
+ Example with prime256v1 (P-256):
437
+ >>> from charm.toolbox.eccurve import prime256v1
438
+ >>> group = ECGroup(prime256v1)
439
+ >>> dkls = DKLS23(group, threshold=2, num_parties=3, party_id=1)
440
+
441
+ Full Example
442
+ ------------
443
+ >>> from charm.toolbox.eccurve import secp256k1
444
+ >>> group = ECGroup(secp256k1)
445
+ >>> dkls = DKLS23(group, threshold=2, num_parties=3)
446
+ >>> g = group.random(G)
447
+ >>>
448
+ >>> # Step 1: Distributed Key Generation
449
+ >>> key_shares, public_key = dkls.distributed_keygen(g)
450
+ >>>
451
+ >>> # Step 2: Generate presignatures (can be done offline)
452
+ >>> presignatures = dkls.presign([1, 2], key_shares, g)
453
+ >>>
454
+ >>> # Step 3: Sign a message
455
+ >>> message = b"Hello, threshold ECDSA!"
456
+ >>> signature = dkls.sign([1, 2], presignatures, key_shares, message, g)
457
+ >>>
458
+ >>> # Step 4: Verify (standard ECDSA verification)
459
+ >>> dkls.verify(public_key, signature, message, g)
460
+ True
461
+ """
462
+
463
+ def __init__(self, groupObj: ECGroupType, threshold: int = 2, num_parties: int = 3) -> None:
464
+ """
465
+ Initialize DKLS23 threshold ECDSA.
466
+
467
+ Args:
468
+ groupObj: An ECGroup instance (e.g., ECGroup(secp256k1))
469
+ threshold: Minimum number of parties required to sign (t)
470
+ num_parties: Total number of parties (n)
471
+
472
+ Raises:
473
+ ValueError: If threshold > num_parties or threshold < 1
474
+ """
475
+ PKSig.__init__(self)
476
+ self.group = groupObj
477
+ self.t = threshold
478
+ self.n = num_parties
479
+
480
+ if threshold > num_parties:
481
+ raise ValueError("threshold cannot exceed num_parties")
482
+ if threshold < 1:
483
+ raise ValueError("threshold must be at least 1")
484
+
485
+ # Initialize component protocols
486
+ self._dkg = DKLS23_DKG(groupObj, threshold, num_parties)
487
+ self._presign = DKLS23_Presign(groupObj)
488
+ self._sign = DKLS23_Sign(groupObj)
489
+ self._sharing = ThresholdSharing(groupObj)
490
+
491
+ def keygen(self, securityparam: Optional[int] = None, generator: Optional[GElement] = None) -> Tuple[Dict[PartyId, KeyShare], GElement]:
492
+ """
493
+ Key generation interface (PKSig compatibility).
494
+
495
+ Args:
496
+ securityparam: Security parameter (unused, curve-dependent)
497
+ generator: Generator point g
498
+
499
+ Returns:
500
+ Tuple of (key_shares, public_key)
501
+ """
502
+ if generator is None:
503
+ generator = self.group.random(G)
504
+ return self.distributed_keygen(generator)
505
+
506
+ def distributed_keygen(self, generator: GElement) -> Tuple[Dict[PartyId, KeyShare], GElement]:
507
+ """
508
+ Run the full DKG protocol.
509
+
510
+ Executes all rounds of the distributed key generation protocol
511
+ to produce key shares for all parties and the combined public key.
512
+
513
+ Args:
514
+ generator: Generator point g in the EC group
515
+
516
+ Returns:
517
+ Tuple of (key_shares_dict, public_key)
518
+ - key_shares_dict: {party_id: KeyShare}
519
+ - public_key: Combined public key (EC point)
520
+
521
+ >>> from charm.toolbox.eccurve import secp256k1
522
+ >>> group = ECGroup(secp256k1)
523
+ >>> dkls = DKLS23(group, threshold=2, num_parties=3)
524
+ >>> g = group.random(G)
525
+ >>> key_shares, pk = dkls.distributed_keygen(g)
526
+ >>> len(key_shares) == 3
527
+ True
528
+ >>> all(ks.X == pk for ks in key_shares.values())
529
+ True
530
+ """
531
+ # Generate a shared session ID for all participants (DKG)
532
+ from charm.toolbox.securerandom import OpenSSLRand
533
+ session_id = OpenSSLRand().getRandomBytes(32)
534
+
535
+ # Round 1: Each party generates secret and Feldman commitments
536
+ round1_results = {}
537
+ private_states = {}
538
+ for party_id in range(1, self.n + 1):
539
+ broadcast_msg, private_state = self._dkg.keygen_round1(party_id, generator, session_id)
540
+ round1_results[party_id] = broadcast_msg
541
+ private_states[party_id] = private_state
542
+
543
+ # Collect all round 1 messages
544
+ all_round1_msgs = list(round1_results.values())
545
+
546
+ # Round 2: Generate shares for other parties
547
+ shares_for_others = {}
548
+ states_after_round2 = {}
549
+ for party_id in range(1, self.n + 1):
550
+ shares, state = self._dkg.keygen_round2(
551
+ party_id, private_states[party_id], all_round1_msgs
552
+ )
553
+ shares_for_others[party_id] = shares
554
+ states_after_round2[party_id] = state
555
+
556
+ # Collect shares received by each party
557
+ received_shares = {}
558
+ for receiver in range(1, self.n + 1):
559
+ received_shares[receiver] = {}
560
+ for sender in range(1, self.n + 1):
561
+ received_shares[receiver][sender] = shares_for_others[sender][receiver]
562
+
563
+ # Round 3: Verify shares and compute final key shares
564
+ key_shares = {}
565
+ for party_id in range(1, self.n + 1):
566
+ ks, complaint = self._dkg.keygen_round3(
567
+ party_id,
568
+ states_after_round2[party_id],
569
+ received_shares[party_id],
570
+ all_round1_msgs
571
+ )
572
+ if complaint is not None:
573
+ raise ValueError(f"DKG failed: party {complaint['accuser']} complained about party {complaint['accused']}")
574
+ key_shares[party_id] = ks
575
+
576
+ # All parties should have the same public key
577
+ public_key = key_shares[1].X
578
+
579
+ return key_shares, public_key
580
+
581
+ def presign(self, participants: List[PartyId], key_shares: Dict[PartyId, KeyShare], generator: GElement) -> Dict[PartyId, Presignature]:
582
+ """
583
+ Run presigning for given participants.
584
+
585
+ Executes the 3-round presigning protocol to generate presignatures
586
+ that can later be combined with a message.
587
+
588
+ Args:
589
+ participants: List of participating party IDs (must have at least t)
590
+ key_shares: Dict mapping party_id to KeyShare
591
+ generator: Generator point g
592
+
593
+ Returns:
594
+ Dict mapping party_id to Presignature
595
+
596
+ >>> from charm.toolbox.eccurve import secp256k1
597
+ >>> group = ECGroup(secp256k1)
598
+ >>> dkls = DKLS23(group, threshold=2, num_parties=3)
599
+ >>> g = group.random(G)
600
+ >>> key_shares, pk = dkls.distributed_keygen(g)
601
+ >>> presigs = dkls.presign([1, 2], key_shares, g)
602
+ >>> len(presigs) == 2
603
+ True
604
+ >>> all(p.is_valid() for p in presigs.values())
605
+ True
606
+ """
607
+ if len(participants) < self.t:
608
+ raise ValueError(f"Need at least {self.t} participants, got {len(participants)}")
609
+
610
+ # Generate a shared session ID for all participants
611
+ from charm.toolbox.securerandom import OpenSSLRand
612
+ session_id = OpenSSLRand().getRandomBytes(32)
613
+
614
+ # Round 1: Each party generates nonce share and prepares MtA
615
+ r1_results = {}
616
+ states = {}
617
+ for pid in participants:
618
+ broadcast, state = self._presign.presign_round1(
619
+ pid, key_shares[pid].x_i, participants, generator, session_id=session_id
620
+ )
621
+ r1_results[pid] = broadcast
622
+ states[pid] = state
623
+
624
+ # Round 2: Process MtA and share gamma commitments
625
+ r2_results = {}
626
+ p2p_msgs = {}
627
+ for pid in participants:
628
+ broadcast, p2p, state = self._presign.presign_round2(pid, states[pid], r1_results)
629
+ r2_results[pid] = broadcast
630
+ p2p_msgs[pid] = p2p
631
+ states[pid] = state
632
+
633
+ # Collect p2p messages from round 2 for each party
634
+ p2p_received_r2 = {}
635
+ for receiver in participants:
636
+ p2p_received_r2[receiver] = {}
637
+ for sender in participants:
638
+ if sender != receiver:
639
+ p2p_received_r2[receiver][sender] = p2p_msgs[sender][receiver]
640
+
641
+ # Round 3: Process MtA sender completions and send OT data
642
+ r3_p2p_msgs = {}
643
+ for pid in participants:
644
+ p2p_r3, state = self._presign.presign_round3(pid, states[pid], r2_results, p2p_received_r2[pid])
645
+ r3_p2p_msgs[pid] = p2p_r3
646
+ states[pid] = state
647
+
648
+ # Collect p2p messages from round 3 for each party
649
+ p2p_received_r3 = {}
650
+ for receiver in participants:
651
+ p2p_received_r3[receiver] = {}
652
+ for sender in participants:
653
+ if sender != receiver:
654
+ p2p_received_r3[receiver][sender] = r3_p2p_msgs[sender][receiver]
655
+
656
+ # Round 4: Complete MtA receiver side and compute presignature
657
+ presignatures = {}
658
+ for pid in participants:
659
+ presig, failed_parties = self._presign.presign_round4(pid, states[pid], p2p_received_r3[pid])
660
+ if failed_parties:
661
+ raise ValueError(f"Presigning failed: parties {failed_parties} had commitment verification failures")
662
+ presignatures[pid] = presig
663
+
664
+ return presignatures
665
+
666
+ def sign(self, participants: List[PartyId], presignatures: Dict[PartyId, Presignature], key_shares: Dict[PartyId, KeyShare], message: bytes, generator: GElement, prehashed: bool = False) -> 'ThresholdSignature':
667
+ """
668
+ Sign a message using presignatures.
669
+
670
+ Combines presignatures with a message to produce a standard ECDSA signature.
671
+
672
+ Args:
673
+ participants: List of participating party IDs
674
+ presignatures: Dict mapping party_id to Presignature
675
+ key_shares: Dict mapping party_id to KeyShare
676
+ message: Message bytes to sign
677
+ generator: Generator point g
678
+ prehashed: If True, message is already a 32-byte hash (no additional hashing).
679
+ Use this for protocols like XRPL that provide their own signing hash.
680
+
681
+ Returns:
682
+ ThresholdSignature object with (r, s)
683
+
684
+ >>> from charm.toolbox.eccurve import secp256k1
685
+ >>> group = ECGroup(secp256k1)
686
+ >>> dkls = DKLS23(group, threshold=2, num_parties=3)
687
+ >>> g = group.random(G)
688
+ >>> key_shares, pk = dkls.distributed_keygen(g)
689
+ >>> presigs = dkls.presign([1, 2], key_shares, g)
690
+ >>> msg = b"Hello, threshold ECDSA!"
691
+ >>> sig = dkls.sign([1, 2], presigs, key_shares, msg, g)
692
+ >>> isinstance(sig, ThresholdSignature)
693
+ True
694
+ """
695
+ if len(participants) < self.t:
696
+ raise ValueError(f"Need at least {self.t} participants, got {len(participants)}")
697
+
698
+ # Step 1: Compute delta = sum of all delta_i shares
699
+ # In DKLS23, delta = k*gamma where gamma is random blinding
700
+ # This is safe to reveal because gamma makes it uniformly random
701
+ delta = self.group.init(ZR, 0)
702
+ for pid in participants:
703
+ delta = delta + presignatures[pid].delta_i
704
+
705
+ # Compute delta^{-1} for use in signature shares
706
+ delta_inv = delta ** -1
707
+
708
+ # Step 2: Each party generates their signature share
709
+ # s_i = delta^{-1} * (e * gamma_i + r * sigma_i)
710
+ signature_shares = {}
711
+ for pid in participants:
712
+ s_i, proof = self._sign.sign_round1(
713
+ pid, presignatures[pid], key_shares[pid], message, participants, delta_inv, prehashed
714
+ )
715
+ signature_shares[pid] = s_i
716
+
717
+ # Step 3: Combine signature shares (simple sum, no Lagrange needed)
718
+ # s = sum(s_i) = delta^{-1} * (e * gamma + r * gamma*x) = k^{-1}(e + rx)
719
+ first_pid = participants[0]
720
+ signature = self._sign.combine_signatures(
721
+ signature_shares, presignatures[first_pid], participants
722
+ )
723
+
724
+ return signature
725
+
726
+ def verify(self, public_key: GElement, signature: Union['ThresholdSignature', Tuple[ZRElement, ZRElement]], message: bytes, generator: GElement) -> bool:
727
+ """
728
+ Verify signature (standard ECDSA).
729
+
730
+ Verifies that the signature is valid for the message and public key.
731
+ Uses standard ECDSA verification, compatible with any ECDSA implementation.
732
+
733
+ Args:
734
+ public_key: The combined public key (EC point)
735
+ signature: ThresholdSignature or tuple (r, s)
736
+ message: The message that was signed
737
+ generator: Generator point g
738
+
739
+ Returns:
740
+ True if valid, False otherwise
741
+
742
+ >>> from charm.toolbox.eccurve import secp256k1
743
+ >>> group = ECGroup(secp256k1)
744
+ >>> dkls = DKLS23(group, threshold=2, num_parties=3)
745
+ >>> g = group.random(G)
746
+ >>> key_shares, pk = dkls.distributed_keygen(g)
747
+ >>> presigs = dkls.presign([1, 2], key_shares, g)
748
+ >>> msg = b"Test message"
749
+ >>> sig = dkls.sign([1, 2], presigs, key_shares, msg, g)
750
+ >>> dkls.verify(pk, sig, msg, g)
751
+ True
752
+ >>> # Verify fails with wrong message
753
+ >>> dkls.verify(pk, sig, b"Wrong message", g)
754
+ False
755
+ """
756
+ return self._sign.verify(public_key, signature, message, generator)
757
+
758
+
759
+ if __name__ == "__main__":
760
+ import doctest
761
+ doctest.testmod()