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,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()
|