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,1089 @@
|
|
|
1
|
+
'''
|
|
2
|
+
DKLS23 Presigning Protocol (3 rounds) 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 presigning
|
|
10
|
+
* setting: Elliptic Curve DDH-hard group
|
|
11
|
+
* assumption: DDH + OT security
|
|
12
|
+
|
|
13
|
+
This module implements the presigning phase of the DKLS23 threshold ECDSA
|
|
14
|
+
protocol. Presignatures can be computed offline before the message is known,
|
|
15
|
+
then combined with messages later for efficient signing.
|
|
16
|
+
|
|
17
|
+
Protocol Overview:
|
|
18
|
+
1. Round 1: Each party samples random k_i (nonce share) and γ_i (blinding).
|
|
19
|
+
Computes Γ_i = g^{γ_i} and commits. Prepares MtA inputs.
|
|
20
|
+
|
|
21
|
+
2. Round 2: Parties run pairwise MtA to compute additive shares of:
|
|
22
|
+
- k * γ (used to compute R)
|
|
23
|
+
- k * x (used for signature share)
|
|
24
|
+
Each party shares their Γ_i values.
|
|
25
|
+
|
|
26
|
+
3. Round 3: Combine MtA results. Compute δ = k * γ mod q, then
|
|
27
|
+
compute R = (∏Γ_i)^{δ^-1}. Derive r = R.x mod q.
|
|
28
|
+
Compute χ_i shares for k*x.
|
|
29
|
+
|
|
30
|
+
:Authors: Elton de Souza
|
|
31
|
+
:Date: 01/2026
|
|
32
|
+
|
|
33
|
+
Implementation Notes
|
|
34
|
+
--------------------
|
|
35
|
+
R Point Computation Deviation:
|
|
36
|
+
This implementation computes R = g^k (as the product of g^{k_i} from all parties)
|
|
37
|
+
rather than R = Gamma^{delta^{-1}} as specified in the DKLS23 paper. These approaches
|
|
38
|
+
are mathematically equivalent:
|
|
39
|
+
- Paper: R = Gamma^{delta^{-1}} = (g^gamma)^{(k*gamma)^{-1}} = g^{k^{-1}}
|
|
40
|
+
- Implementation: R = prod(g^{k_i}) = g^{sum(k_i)} = g^k
|
|
41
|
+
|
|
42
|
+
The signature formula is adjusted accordingly in dkls23_sign.py to account for
|
|
43
|
+
this difference. Instead of using delta^{-1} during presigning, we incorporate
|
|
44
|
+
the necessary adjustments during the signing phase.
|
|
45
|
+
|
|
46
|
+
See lines ~706-715 for the R point computation.
|
|
47
|
+
'''
|
|
48
|
+
|
|
49
|
+
from typing import Dict, List, Tuple, Optional, Any
|
|
50
|
+
|
|
51
|
+
from charm.toolbox.ecgroup import ECGroup, ZR, G
|
|
52
|
+
from charm.toolbox.eccurve import secp256k1
|
|
53
|
+
from charm.toolbox.mta import MtA
|
|
54
|
+
from charm.toolbox.threshold_sharing import ThresholdSharing
|
|
55
|
+
from charm.toolbox.securerandom import SecureRandomFactory
|
|
56
|
+
import hashlib
|
|
57
|
+
|
|
58
|
+
# Type aliases for charm-crypto types
|
|
59
|
+
ZRElement = Any # Scalar field element
|
|
60
|
+
GElement = Any # Group/curve point element
|
|
61
|
+
ECGroupType = Any # ECGroup instance
|
|
62
|
+
PartyId = int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SecurityAbort(Exception):
|
|
66
|
+
"""
|
|
67
|
+
Exception raised when the protocol must abort due to a security violation.
|
|
68
|
+
|
|
69
|
+
This exception is raised when a participant fails verification checks,
|
|
70
|
+
such as commitment mismatches, invalid proofs, or other security-critical
|
|
71
|
+
failures that indicate malicious or faulty behavior.
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
failed_parties: List of party IDs that failed verification
|
|
75
|
+
message: Description of the security violation
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, message, failed_parties=None):
|
|
79
|
+
self.failed_parties = failed_parties or []
|
|
80
|
+
self.message = message
|
|
81
|
+
super().__init__(f"{message} (failed parties: {self.failed_parties})")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Presignature:
|
|
85
|
+
"""
|
|
86
|
+
Holds a presignature share for threshold signing.
|
|
87
|
+
|
|
88
|
+
A presignature contains all the precomputed values needed to generate
|
|
89
|
+
a signature share once the message is known.
|
|
90
|
+
|
|
91
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
92
|
+
>>> group = ECGroup(secp256k1)
|
|
93
|
+
>>> g = group.random(G)
|
|
94
|
+
>>> k_share = group.random(ZR)
|
|
95
|
+
>>> chi_share = group.random(ZR)
|
|
96
|
+
>>> R = g ** group.random(ZR)
|
|
97
|
+
>>> r = group.zr(R)
|
|
98
|
+
>>> ps = Presignature(1, R, r, k_share, chi_share, [1, 2, 3])
|
|
99
|
+
>>> ps.party_id
|
|
100
|
+
1
|
|
101
|
+
>>> ps.is_valid()
|
|
102
|
+
True
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, party_id: PartyId, R: GElement, r: ZRElement, k_share: ZRElement, chi_share: ZRElement, participants: List[PartyId], gamma_i: Optional[ZRElement] = None, delta_i: Optional[ZRElement] = None) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Initialize a presignature share.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
party_id: The party's identifier
|
|
111
|
+
R: The R point (g^k where k is the combined nonce)
|
|
112
|
+
r: The x-coordinate of R (mod q)
|
|
113
|
+
k_share: Party's share of k (the nonce)
|
|
114
|
+
chi_share: Party's share of chi = k * x (nonce times private key)
|
|
115
|
+
participants: List of party IDs that participated
|
|
116
|
+
gamma_i: Party's blinding factor share (for delta-based signing)
|
|
117
|
+
delta_i: Party's share of delta = k * gamma
|
|
118
|
+
"""
|
|
119
|
+
self.party_id = party_id
|
|
120
|
+
self.R = R # The R point (g^k)
|
|
121
|
+
self.r = r # r = R.x mod q
|
|
122
|
+
self.k_i = k_share # Party's share of k
|
|
123
|
+
self.chi_i = chi_share # Party's share of chi = k * x
|
|
124
|
+
self.participants = participants
|
|
125
|
+
self.gamma_i = gamma_i # Blinding factor share
|
|
126
|
+
self.delta_i = delta_i # Share of k * gamma
|
|
127
|
+
|
|
128
|
+
def is_valid(self) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Check if presignature is well-formed.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
True if presignature contains valid components, False otherwise.
|
|
134
|
+
"""
|
|
135
|
+
return (
|
|
136
|
+
self.party_id is not None and
|
|
137
|
+
self.R is not None and
|
|
138
|
+
self.r is not None and
|
|
139
|
+
self.k_i is not None and
|
|
140
|
+
self.chi_i is not None and
|
|
141
|
+
len(self.participants) > 0
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def __repr__(self) -> str:
|
|
145
|
+
return f"Presignature(party_id={self.party_id}, participants={self.participants})"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class DKLS23_Presign:
|
|
149
|
+
"""
|
|
150
|
+
DKLS23 Presigning Protocol (3 rounds)
|
|
151
|
+
|
|
152
|
+
Generates presignatures that can later be combined with a message
|
|
153
|
+
to produce a threshold ECDSA signature.
|
|
154
|
+
|
|
155
|
+
Curve Agnostic
|
|
156
|
+
--------------
|
|
157
|
+
This implementation supports any elliptic curve group that is DDH-hard.
|
|
158
|
+
The curve is specified via the groupObj parameter.
|
|
159
|
+
|
|
160
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
161
|
+
>>> group = ECGroup(secp256k1)
|
|
162
|
+
>>> presign = DKLS23_Presign(group)
|
|
163
|
+
>>> g = group.random(G)
|
|
164
|
+
>>> # Simulate key shares for 2-of-3 threshold
|
|
165
|
+
>>> x = group.random(ZR) # Full private key (for simulation)
|
|
166
|
+
>>> ts = ThresholdSharing(group)
|
|
167
|
+
>>> x_shares = ts.share(x, 2, 3)
|
|
168
|
+
>>> participants = [1, 2, 3]
|
|
169
|
+
>>> # Round 1: Each party generates nonce share and prepares MtA
|
|
170
|
+
>>> r1_results = {}
|
|
171
|
+
>>> states = {}
|
|
172
|
+
>>> for pid in participants:
|
|
173
|
+
... broadcast, state = presign.presign_round1(pid, x_shares[pid], participants, g)
|
|
174
|
+
... r1_results[pid] = broadcast
|
|
175
|
+
... states[pid] = state
|
|
176
|
+
>>> # Round 2: Process MtA and share gamma commitments
|
|
177
|
+
>>> r2_results = {}
|
|
178
|
+
>>> p2p_msgs = {}
|
|
179
|
+
>>> for pid in participants:
|
|
180
|
+
... broadcast, p2p, state = presign.presign_round2(pid, states[pid], r1_results)
|
|
181
|
+
... r2_results[pid] = broadcast
|
|
182
|
+
... p2p_msgs[pid] = p2p
|
|
183
|
+
... states[pid] = state
|
|
184
|
+
>>> # Collect p2p messages for each party
|
|
185
|
+
>>> p2p_received = {}
|
|
186
|
+
>>> for receiver in participants:
|
|
187
|
+
... p2p_received[receiver] = {}
|
|
188
|
+
... for sender in participants:
|
|
189
|
+
... if sender != receiver:
|
|
190
|
+
... p2p_received[receiver][sender] = p2p_msgs[sender][receiver]
|
|
191
|
+
>>> # Round 3: Complete MtA and compute R point
|
|
192
|
+
>>> presigs = {}
|
|
193
|
+
>>> for pid in participants:
|
|
194
|
+
... presig = presign.presign_round3(pid, states[pid], r2_results, p2p_received[pid])
|
|
195
|
+
... presigs[pid] = presig
|
|
196
|
+
>>> # All parties should have the same R point
|
|
197
|
+
>>> presigs[1].R == presigs[2].R == presigs[3].R
|
|
198
|
+
True
|
|
199
|
+
>>> # All presignatures should be valid
|
|
200
|
+
>>> all(p.is_valid() for p in presigs.values())
|
|
201
|
+
True
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def __init__(self, groupObj: ECGroupType) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Initialize the presigning protocol.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
groupObj: An ECGroup instance (e.g., ECGroup(secp256k1))
|
|
210
|
+
|
|
211
|
+
Raises:
|
|
212
|
+
ValueError: If groupObj is None
|
|
213
|
+
"""
|
|
214
|
+
if groupObj is None:
|
|
215
|
+
raise ValueError("groupObj cannot be None")
|
|
216
|
+
self.group = groupObj
|
|
217
|
+
self.order = groupObj.order()
|
|
218
|
+
self.mta = MtA(groupObj)
|
|
219
|
+
self._sharing = ThresholdSharing(groupObj)
|
|
220
|
+
self._rand = SecureRandomFactory.getInstance()
|
|
221
|
+
|
|
222
|
+
def _compute_schnorr_challenge_hash(self, generator: GElement, public_point: GElement, commitment: GElement, party_id: PartyId, session_id: bytes) -> bytes:
|
|
223
|
+
"""
|
|
224
|
+
Compute Fiat-Shamir challenge hash for Schnorr proofs.
|
|
225
|
+
|
|
226
|
+
Uses SHA-256 with domain separation to compute a deterministic
|
|
227
|
+
challenge for non-interactive Schnorr proofs of discrete log knowledge.
|
|
228
|
+
|
|
229
|
+
Parameters
|
|
230
|
+
----------
|
|
231
|
+
generator : G element
|
|
232
|
+
The base generator point g.
|
|
233
|
+
public_point : G element
|
|
234
|
+
The public point being proven (R_i = g^{k_i}).
|
|
235
|
+
commitment : G element
|
|
236
|
+
The Schnorr commitment T = g^r.
|
|
237
|
+
party_id : int or str
|
|
238
|
+
Party identifier for domain separation.
|
|
239
|
+
session_id : bytes or str
|
|
240
|
+
Session identifier for domain separation.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
bytes
|
|
245
|
+
32-byte SHA-256 hash to be used as challenge input.
|
|
246
|
+
"""
|
|
247
|
+
h = hashlib.sha256()
|
|
248
|
+
h.update(b"SCHNORR_R_VALIDITY_PROOF") # Domain separator
|
|
249
|
+
h.update(self.group.serialize(generator))
|
|
250
|
+
h.update(self.group.serialize(public_point))
|
|
251
|
+
h.update(self.group.serialize(commitment))
|
|
252
|
+
h.update(str(party_id).encode('utf-8'))
|
|
253
|
+
if session_id:
|
|
254
|
+
if isinstance(session_id, bytes):
|
|
255
|
+
h.update(session_id)
|
|
256
|
+
else:
|
|
257
|
+
h.update(str(session_id).encode('utf-8'))
|
|
258
|
+
return h.digest()
|
|
259
|
+
|
|
260
|
+
def _schnorr_prove_dlog(self, secret: ZRElement, public_point: GElement, generator: GElement, party_id: PartyId, session_id: bytes) -> Dict[str, Any]:
|
|
261
|
+
"""
|
|
262
|
+
Generate a Schnorr proof of knowledge of discrete log.
|
|
263
|
+
|
|
264
|
+
Proves knowledge of 'secret' such that public_point = generator^secret.
|
|
265
|
+
Uses Fiat-Shamir transform for non-interactivity.
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
----------
|
|
269
|
+
secret : ZR element
|
|
270
|
+
The secret exponent (k_i).
|
|
271
|
+
public_point : G element
|
|
272
|
+
The public point (R_i = g^{k_i}).
|
|
273
|
+
generator : G element
|
|
274
|
+
The base generator g.
|
|
275
|
+
party_id : int or str
|
|
276
|
+
Party identifier for domain separation.
|
|
277
|
+
session_id : bytes or str
|
|
278
|
+
Session identifier for domain separation.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
dict
|
|
283
|
+
Proof containing 'T' (commitment) and 's' (response).
|
|
284
|
+
"""
|
|
285
|
+
# Sample random nonce
|
|
286
|
+
r = self.group.random(ZR)
|
|
287
|
+
|
|
288
|
+
# Commitment: T = g^r
|
|
289
|
+
T = generator ** r
|
|
290
|
+
|
|
291
|
+
# Fiat-Shamir challenge: c = H(g || R_i || T || party_id || session_id)
|
|
292
|
+
c_bytes = self._compute_schnorr_challenge_hash(
|
|
293
|
+
generator, public_point, T, party_id, session_id
|
|
294
|
+
)
|
|
295
|
+
c = self.group.hash(c_bytes, ZR)
|
|
296
|
+
|
|
297
|
+
# Response: s = r + c * secret
|
|
298
|
+
s = r + c * secret
|
|
299
|
+
|
|
300
|
+
return {'T': T, 's': s}
|
|
301
|
+
|
|
302
|
+
def _schnorr_verify_dlog(self, public_point: GElement, proof: Dict[str, Any], generator: GElement, party_id: PartyId, session_id: bytes) -> bool:
|
|
303
|
+
"""
|
|
304
|
+
Verify a Schnorr proof of knowledge of discrete log.
|
|
305
|
+
|
|
306
|
+
Verifies that the prover knows 'secret' such that public_point = generator^secret.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
public_point : G element
|
|
311
|
+
The public point being verified (R_i).
|
|
312
|
+
proof : dict
|
|
313
|
+
Proof with 'T' (commitment) and 's' (response).
|
|
314
|
+
generator : G element
|
|
315
|
+
The base generator g.
|
|
316
|
+
party_id : int or str
|
|
317
|
+
Party identifier of the prover.
|
|
318
|
+
session_id : bytes or str
|
|
319
|
+
Session identifier.
|
|
320
|
+
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
bool
|
|
324
|
+
True if proof is valid, False otherwise.
|
|
325
|
+
"""
|
|
326
|
+
T = proof.get('T')
|
|
327
|
+
s = proof.get('s')
|
|
328
|
+
|
|
329
|
+
if T is None or s is None:
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
# Recompute challenge: c = H(g || R_i || T || party_id || session_id)
|
|
333
|
+
c_bytes = self._compute_schnorr_challenge_hash(
|
|
334
|
+
generator, public_point, T, party_id, session_id
|
|
335
|
+
)
|
|
336
|
+
c = self.group.hash(c_bytes, ZR)
|
|
337
|
+
|
|
338
|
+
# Verify: g^s == T * R_i^c
|
|
339
|
+
lhs = generator ** s
|
|
340
|
+
rhs = T * (public_point ** c)
|
|
341
|
+
|
|
342
|
+
return lhs == rhs
|
|
343
|
+
|
|
344
|
+
def _compute_commitment(self, *values: Any, session_id: Optional[bytes] = None, participants: Optional[List[PartyId]] = None) -> bytes:
|
|
345
|
+
"""
|
|
346
|
+
Compute a cryptographic commitment to one or more values.
|
|
347
|
+
|
|
348
|
+
Uses group.hash() with domain separation to hash the serialized values
|
|
349
|
+
into a fixed-size commitment. Optionally binds the commitment to a
|
|
350
|
+
session ID and participant set to prevent replay attacks across sessions.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
*values : various
|
|
355
|
+
Values to commit to. Each value is serialized to bytes before hashing.
|
|
356
|
+
Supported types: bytes, str, int, ZR elements, G elements.
|
|
357
|
+
session_id : bytes or str, optional
|
|
358
|
+
Session identifier to bind commitment to specific protocol instance.
|
|
359
|
+
participants : list, optional
|
|
360
|
+
List of participant IDs to bind commitment to specific party set.
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
bytes
|
|
365
|
+
Serialized hash output serving as the commitment.
|
|
366
|
+
|
|
367
|
+
Notes
|
|
368
|
+
-----
|
|
369
|
+
This is a non-hiding commitment (the commitment reveals the value if
|
|
370
|
+
the value space is small). For hiding commitments, use Pedersen VSS.
|
|
371
|
+
|
|
372
|
+
Example
|
|
373
|
+
-------
|
|
374
|
+
>>> commitment = self._compute_commitment(gamma_point, session_id=b"session123")
|
|
375
|
+
"""
|
|
376
|
+
# Build tuple with domain separator and context
|
|
377
|
+
hash_input = [b"PRESIGN_COMMIT:"]
|
|
378
|
+
|
|
379
|
+
# Include session ID if provided
|
|
380
|
+
# Note: Convert to bytes explicitly to handle Bytes subclass from securerandom
|
|
381
|
+
if session_id is not None:
|
|
382
|
+
if isinstance(session_id, bytes):
|
|
383
|
+
hash_input.append(bytes(session_id))
|
|
384
|
+
else:
|
|
385
|
+
hash_input.append(str(session_id).encode('utf-8'))
|
|
386
|
+
|
|
387
|
+
# Include sorted participant list if provided
|
|
388
|
+
if participants is not None:
|
|
389
|
+
sorted_participants = sorted(participants)
|
|
390
|
+
participant_bytes = ','.join(str(p) for p in sorted_participants).encode('utf-8')
|
|
391
|
+
hash_input.append(participant_bytes)
|
|
392
|
+
|
|
393
|
+
# Include the actual values
|
|
394
|
+
hash_input.extend(values)
|
|
395
|
+
|
|
396
|
+
# Hash to ZR and serialize to get bytes
|
|
397
|
+
result = self.group.hash(tuple(hash_input), target_type=ZR)
|
|
398
|
+
return self.group.serialize(result)
|
|
399
|
+
|
|
400
|
+
def presign_round1(self, party_id: PartyId, key_share: Any, participants: List[PartyId], generator: GElement, session_id: bytes) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
|
401
|
+
"""
|
|
402
|
+
Round 1: Generate nonce share k_i and MtA inputs.
|
|
403
|
+
|
|
404
|
+
Each party samples random k_i (nonce share) and γ_i (blinding factor).
|
|
405
|
+
Computes commitment to Γ_i = g^{γ_i} and prepares for MtA with
|
|
406
|
+
other parties. The commitment is bound to the session ID and participant
|
|
407
|
+
set to prevent cross-session attacks.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
party_id: This party's identifier
|
|
411
|
+
key_share: Party's share of the private key x_i
|
|
412
|
+
participants: List of all participating party IDs
|
|
413
|
+
generator: Generator point g in the EC group
|
|
414
|
+
session_id: Required session identifier (bytes or str). Must be unique
|
|
415
|
+
per protocol instance and shared across all participants to prevent
|
|
416
|
+
replay attacks.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Tuple of (broadcast_msg, state)
|
|
420
|
+
- broadcast_msg: Message to broadcast to all parties (includes session_id)
|
|
421
|
+
- state: Private state for next round
|
|
422
|
+
|
|
423
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
424
|
+
>>> group = ECGroup(secp256k1)
|
|
425
|
+
>>> presign = DKLS23_Presign(group)
|
|
426
|
+
>>> g = group.random(G)
|
|
427
|
+
>>> x_i = group.random(ZR)
|
|
428
|
+
>>> msg, state = presign.presign_round1(1, x_i, [1, 2, 3], g, session_id=b"test-session")
|
|
429
|
+
>>> 'party_id' in msg and 'Gamma_commitment' in msg
|
|
430
|
+
True
|
|
431
|
+
>>> 'k_i' in state and 'gamma_i' in state
|
|
432
|
+
True
|
|
433
|
+
>>> 'session_id' in msg # Session ID included in broadcast
|
|
434
|
+
True
|
|
435
|
+
"""
|
|
436
|
+
# Validate session_id is provided and non-empty
|
|
437
|
+
if session_id is None:
|
|
438
|
+
raise ValueError("session_id is required for replay attack prevention")
|
|
439
|
+
if isinstance(session_id, (bytes, str)) and len(session_id) == 0:
|
|
440
|
+
raise ValueError("session_id cannot be empty")
|
|
441
|
+
|
|
442
|
+
# Sample random nonce share k_i
|
|
443
|
+
k_i = self.group.random(ZR)
|
|
444
|
+
|
|
445
|
+
# Sample random blinding factor γ_i
|
|
446
|
+
gamma_i = self.group.random(ZR)
|
|
447
|
+
|
|
448
|
+
# Compute Γ_i = g^{γ_i}
|
|
449
|
+
Gamma_i = generator ** gamma_i
|
|
450
|
+
|
|
451
|
+
# Compute commitment to Γ_i, bound to session and participants
|
|
452
|
+
Gamma_commitment = self._compute_commitment(
|
|
453
|
+
Gamma_i, session_id=session_id, participants=participants
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# Compute Lagrange coefficient for this party
|
|
457
|
+
# This converts polynomial (Shamir) key shares to additive shares
|
|
458
|
+
lambda_i = self._sharing.lagrange_coefficient(participants, party_id, x=0)
|
|
459
|
+
weighted_key_share = lambda_i * key_share # x_i * L_i(0)
|
|
460
|
+
|
|
461
|
+
# Prepare MtA state for each pair
|
|
462
|
+
# We'll need to run MtA for:
|
|
463
|
+
# - k_i * gamma_j (for computing delta = k*gamma)
|
|
464
|
+
# - gamma_i * (lambda_j * x_j) (for computing sigma = gamma*x, used in signature)
|
|
465
|
+
# Need separate MtA instances for each because they use different random alphas
|
|
466
|
+
mta_states = {}
|
|
467
|
+
mta_round1_msgs = {}
|
|
468
|
+
mta_round1_msgs_sigma = {} # For gamma*x computation
|
|
469
|
+
for other_id in participants:
|
|
470
|
+
if other_id != party_id:
|
|
471
|
+
# Prepare MtA for k_i * gamma_j (delta computation)
|
|
472
|
+
mta_instance_gamma = MtA(self.group)
|
|
473
|
+
mta_msg_gamma = mta_instance_gamma.sender_round1(k_i)
|
|
474
|
+
|
|
475
|
+
# Prepare MtA for gamma_i * x_j (sigma = gamma*x computation)
|
|
476
|
+
mta_instance_sigma = MtA(self.group)
|
|
477
|
+
mta_msg_sigma = mta_instance_sigma.sender_round1(gamma_i)
|
|
478
|
+
|
|
479
|
+
mta_states[other_id] = {
|
|
480
|
+
'mta_sender': mta_instance_gamma, # For k*gamma (delta)
|
|
481
|
+
'mta_sender_sigma': mta_instance_sigma, # For gamma*x (sigma)
|
|
482
|
+
}
|
|
483
|
+
mta_round1_msgs[other_id] = mta_msg_gamma
|
|
484
|
+
mta_round1_msgs_sigma[other_id] = mta_msg_sigma
|
|
485
|
+
|
|
486
|
+
# Broadcast message
|
|
487
|
+
broadcast_msg = {
|
|
488
|
+
'party_id': party_id,
|
|
489
|
+
'session_id': session_id,
|
|
490
|
+
'Gamma_commitment': Gamma_commitment,
|
|
491
|
+
'mta_k_msgs': mta_round1_msgs, # MtA messages for k_i * gamma_j
|
|
492
|
+
'mta_gamma_x_msgs': mta_round1_msgs_sigma # MtA messages for gamma_i * x_j
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
# Private state
|
|
496
|
+
state = {
|
|
497
|
+
'party_id': party_id,
|
|
498
|
+
'session_id': session_id,
|
|
499
|
+
'key_share': key_share,
|
|
500
|
+
'weighted_key_share': weighted_key_share, # L_i(0) * x_i for additive reconstruction
|
|
501
|
+
'k_i': k_i,
|
|
502
|
+
'gamma_i': gamma_i,
|
|
503
|
+
'Gamma_i': Gamma_i,
|
|
504
|
+
'generator': generator,
|
|
505
|
+
'participants': participants,
|
|
506
|
+
'mta_states': mta_states
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return broadcast_msg, state
|
|
510
|
+
|
|
511
|
+
def presign_round2(self, party_id: PartyId, state: Dict[str, Any], all_round1_msgs: Dict[PartyId, Dict[str, Any]]) -> Tuple[Dict[str, Any], Dict[PartyId, Dict[str, Any]], Dict[str, Any]]:
|
|
512
|
+
"""
|
|
513
|
+
Round 2: Process MtA and generate gamma shares.
|
|
514
|
+
|
|
515
|
+
Parties run MtA to convert k_i * gamma_j to additive shares.
|
|
516
|
+
Also share R_i = g^{k_i} commitments and reveal Γ_i values.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
party_id: This party's identifier
|
|
520
|
+
state: Private state from round 1
|
|
521
|
+
all_round1_msgs: Dictionary {party_id: broadcast_msg} from round 1
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
Tuple of (broadcast_msg, p2p_msgs, state)
|
|
525
|
+
- broadcast_msg: Message to broadcast to all parties
|
|
526
|
+
- p2p_msgs: Dictionary {recipient_id: message} for point-to-point
|
|
527
|
+
- state: Updated private state
|
|
528
|
+
|
|
529
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
530
|
+
>>> group = ECGroup(secp256k1)
|
|
531
|
+
>>> presign = DKLS23_Presign(group)
|
|
532
|
+
>>> g = group.random(G)
|
|
533
|
+
>>> x_i = group.random(ZR)
|
|
534
|
+
>>> msg1, state1 = presign.presign_round1(1, x_i, [1, 2], g)
|
|
535
|
+
>>> msg2, state2 = presign.presign_round1(2, x_i, [1, 2], g)
|
|
536
|
+
>>> all_r1 = {1: msg1, 2: msg2}
|
|
537
|
+
>>> broadcast, p2p, new_state = presign.presign_round2(1, state1, all_r1)
|
|
538
|
+
>>> 'Gamma_i' in broadcast
|
|
539
|
+
True
|
|
540
|
+
"""
|
|
541
|
+
k_i = state['k_i']
|
|
542
|
+
gamma_i = state['gamma_i']
|
|
543
|
+
Gamma_i = state['Gamma_i']
|
|
544
|
+
key_share = state['key_share']
|
|
545
|
+
weighted_key_share = state['weighted_key_share'] # L_i(0) * x_i
|
|
546
|
+
participants = state['participants']
|
|
547
|
+
generator = state['generator']
|
|
548
|
+
mta_states = state['mta_states']
|
|
549
|
+
|
|
550
|
+
# Verify we have messages from all participants
|
|
551
|
+
for pid in participants:
|
|
552
|
+
if pid not in all_round1_msgs:
|
|
553
|
+
raise ValueError(f"Missing round 1 message from party {pid}")
|
|
554
|
+
|
|
555
|
+
# Process received MtA messages and respond
|
|
556
|
+
# We respond to:
|
|
557
|
+
# - k_j * gamma_i (for delta = k*gamma)
|
|
558
|
+
# - gamma_j * (L_i * x_i) (for sigma = gamma*x using Lagrange-weighted shares)
|
|
559
|
+
mta_results = {}
|
|
560
|
+
p2p_msgs = {}
|
|
561
|
+
|
|
562
|
+
for other_id in participants:
|
|
563
|
+
if other_id != party_id:
|
|
564
|
+
other_msg = all_round1_msgs[other_id]
|
|
565
|
+
|
|
566
|
+
# Respond to other party's MtA for k_j * gamma_i (delta computation)
|
|
567
|
+
k_mta_msg = other_msg['mta_k_msgs'].get(party_id)
|
|
568
|
+
# Respond to other party's MtA for gamma_j * (L_i * x_i) (sigma computation)
|
|
569
|
+
gamma_x_mta_msg = other_msg['mta_gamma_x_msgs'].get(party_id)
|
|
570
|
+
|
|
571
|
+
if k_mta_msg and gamma_x_mta_msg:
|
|
572
|
+
# For k_j * gamma_i: we have gamma_i, other has k_j
|
|
573
|
+
mta_receiver_delta = MtA(self.group)
|
|
574
|
+
recv_response_delta, _ = mta_receiver_delta.receiver_round1(gamma_i, k_mta_msg)
|
|
575
|
+
|
|
576
|
+
# For gamma_j * (L_i * x_i): use weighted key share for correct reconstruction
|
|
577
|
+
mta_receiver_sigma = MtA(self.group)
|
|
578
|
+
recv_response_sigma, _ = mta_receiver_sigma.receiver_round1(weighted_key_share, gamma_x_mta_msg)
|
|
579
|
+
|
|
580
|
+
# Note: beta values will be computed in round3 after receiving OT ciphertexts
|
|
581
|
+
mta_results[other_id] = {
|
|
582
|
+
'delta_receiver': mta_receiver_delta,
|
|
583
|
+
'delta_response': recv_response_delta,
|
|
584
|
+
'sigma_receiver': mta_receiver_sigma,
|
|
585
|
+
'sigma_response': recv_response_sigma,
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
p2p_msgs[other_id] = {
|
|
589
|
+
'delta_mta_response': recv_response_delta,
|
|
590
|
+
'sigma_mta_response': recv_response_sigma,
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
# Compute R_i = g^{k_i}
|
|
594
|
+
R_i = generator ** k_i
|
|
595
|
+
|
|
596
|
+
# Generate Schnorr proof for R_i validity (proves knowledge of k_i such that R_i = g^{k_i})
|
|
597
|
+
session_id = state.get('session_id')
|
|
598
|
+
R_i_proof = self._schnorr_prove_dlog(
|
|
599
|
+
secret=k_i,
|
|
600
|
+
public_point=R_i,
|
|
601
|
+
generator=generator,
|
|
602
|
+
party_id=party_id,
|
|
603
|
+
session_id=session_id
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
# Broadcast message - reveal Gamma_i (decommit) and R_i with proof
|
|
607
|
+
broadcast_msg = {
|
|
608
|
+
'party_id': party_id,
|
|
609
|
+
'Gamma_i': Gamma_i,
|
|
610
|
+
'R_i': R_i,
|
|
611
|
+
'R_i_proof': R_i_proof # Schnorr proof of knowledge for R_i
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
# Update state
|
|
615
|
+
updated_state = state.copy()
|
|
616
|
+
updated_state['mta_results'] = mta_results
|
|
617
|
+
updated_state['all_round1_msgs'] = all_round1_msgs
|
|
618
|
+
updated_state['R_i'] = R_i
|
|
619
|
+
|
|
620
|
+
return broadcast_msg, p2p_msgs, updated_state
|
|
621
|
+
|
|
622
|
+
def presign_round3(self, party_id: PartyId, state: Dict[str, Any], all_round2_msgs: Dict[PartyId, Dict[str, Any]], p2p_received: Dict[PartyId, Dict[str, Any]]) -> Tuple[Dict[PartyId, Dict[str, Any]], Dict[str, Any]]:
|
|
623
|
+
"""
|
|
624
|
+
Round 3: Process MtA sender completions and send OT data.
|
|
625
|
+
|
|
626
|
+
Each party completes their role as MtA sender (getting alpha) and
|
|
627
|
+
sends the OT data needed by the receivers.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
party_id: This party's identifier
|
|
631
|
+
state: Private state from round 2
|
|
632
|
+
all_round2_msgs: Dictionary {party_id: broadcast_msg} from round 2
|
|
633
|
+
p2p_received: Dictionary {sender_id: p2p_msg} of messages for this party
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
Tuple of (p2p_msgs, state) where:
|
|
637
|
+
- p2p_msgs: Dictionary {recipient_id: message} with OT data
|
|
638
|
+
- state: Updated private state with alpha values
|
|
639
|
+
|
|
640
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
641
|
+
>>> group = ECGroup(secp256k1)
|
|
642
|
+
>>> presign = DKLS23_Presign(group)
|
|
643
|
+
>>> g = group.random(G)
|
|
644
|
+
>>> ts = ThresholdSharing(group)
|
|
645
|
+
>>> x = group.random(ZR)
|
|
646
|
+
>>> x_shares = ts.share(x, 2, 3)
|
|
647
|
+
>>> participants = [1, 2, 3]
|
|
648
|
+
>>> # Run full protocol
|
|
649
|
+
>>> r1 = {}
|
|
650
|
+
>>> st = {}
|
|
651
|
+
>>> for p in participants:
|
|
652
|
+
... msg, s = presign.presign_round1(p, x_shares[p], participants, g)
|
|
653
|
+
... r1[p], st[p] = msg, s
|
|
654
|
+
>>> r2 = {}
|
|
655
|
+
>>> p2p_r2 = {}
|
|
656
|
+
>>> for p in participants:
|
|
657
|
+
... b, m, s = presign.presign_round2(p, st[p], r1)
|
|
658
|
+
... r2[p], p2p_r2[p], st[p] = b, m, s
|
|
659
|
+
>>> recv_r2 = {}
|
|
660
|
+
>>> for r in participants:
|
|
661
|
+
... recv_r2[r] = {s: p2p_r2[s][r] for s in participants if s != r}
|
|
662
|
+
>>> p2p_r3, st[1] = presign.presign_round3(1, st[1], r2, recv_r2[1])
|
|
663
|
+
>>> 'delta_ot_data' in list(p2p_r3.values())[0]
|
|
664
|
+
True
|
|
665
|
+
"""
|
|
666
|
+
k_i = state['k_i']
|
|
667
|
+
gamma_i = state['gamma_i']
|
|
668
|
+
participants = state['participants']
|
|
669
|
+
mta_states = state['mta_states']
|
|
670
|
+
all_round1_msgs = state['all_round1_msgs']
|
|
671
|
+
session_id = state.get('session_id')
|
|
672
|
+
|
|
673
|
+
# Track failed parties for abort handling
|
|
674
|
+
failed_parties = []
|
|
675
|
+
|
|
676
|
+
# Verify commitments: check that revealed Γ_i matches commitment
|
|
677
|
+
for pid in participants:
|
|
678
|
+
if pid in all_round1_msgs and pid in all_round2_msgs:
|
|
679
|
+
commitment = all_round1_msgs[pid]['Gamma_commitment']
|
|
680
|
+
revealed_Gamma = all_round2_msgs[pid]['Gamma_i']
|
|
681
|
+
computed_commitment = self._compute_commitment(
|
|
682
|
+
revealed_Gamma, session_id=session_id, participants=participants
|
|
683
|
+
)
|
|
684
|
+
if commitment != computed_commitment:
|
|
685
|
+
failed_parties.append(pid)
|
|
686
|
+
|
|
687
|
+
# SECURITY: Verify R_i validity proofs (Schnorr proof of knowledge)
|
|
688
|
+
# This ensures each party knows k_i such that R_i = g^{k_i}
|
|
689
|
+
generator = state['generator']
|
|
690
|
+
for pid in participants:
|
|
691
|
+
if pid in all_round2_msgs and pid not in failed_parties:
|
|
692
|
+
R_i = all_round2_msgs[pid]['R_i']
|
|
693
|
+
R_i_proof = all_round2_msgs[pid].get('R_i_proof')
|
|
694
|
+
|
|
695
|
+
# Missing proof is a security failure
|
|
696
|
+
if R_i_proof is None:
|
|
697
|
+
failed_parties.append(pid)
|
|
698
|
+
continue
|
|
699
|
+
|
|
700
|
+
# Verify Schnorr proof: prover knows k_i such that R_i = g^{k_i}
|
|
701
|
+
if not self._schnorr_verify_dlog(
|
|
702
|
+
public_point=R_i,
|
|
703
|
+
proof=R_i_proof,
|
|
704
|
+
generator=generator,
|
|
705
|
+
party_id=pid,
|
|
706
|
+
session_id=session_id
|
|
707
|
+
):
|
|
708
|
+
failed_parties.append(pid)
|
|
709
|
+
|
|
710
|
+
# SECURITY: Abort if any party failed commitment or R_i proof verification
|
|
711
|
+
if failed_parties:
|
|
712
|
+
raise SecurityAbort(
|
|
713
|
+
"Verification failed during presigning round 3 (commitment or R_i proof)",
|
|
714
|
+
failed_parties=failed_parties
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# Complete MtA sender side and collect OT data to send
|
|
718
|
+
p2p_msgs = {}
|
|
719
|
+
alpha_deltas = {}
|
|
720
|
+
alpha_sigmas = {}
|
|
721
|
+
|
|
722
|
+
for other_id in participants:
|
|
723
|
+
if other_id != party_id:
|
|
724
|
+
if other_id in p2p_received:
|
|
725
|
+
p2p_msg = p2p_received[other_id]
|
|
726
|
+
|
|
727
|
+
# Complete delta MtA as sender
|
|
728
|
+
delta_response = p2p_msg['delta_mta_response']
|
|
729
|
+
mta_sender = mta_states[other_id]['mta_sender']
|
|
730
|
+
alpha_delta, ot_data_delta = mta_sender.sender_round2(delta_response)
|
|
731
|
+
alpha_deltas[other_id] = alpha_delta
|
|
732
|
+
|
|
733
|
+
# Complete sigma MtA as sender
|
|
734
|
+
sigma_response = p2p_msg['sigma_mta_response']
|
|
735
|
+
mta_sender_sigma = mta_states[other_id]['mta_sender_sigma']
|
|
736
|
+
alpha_sigma, ot_data_sigma = mta_sender_sigma.sender_round2(sigma_response)
|
|
737
|
+
alpha_sigmas[other_id] = alpha_sigma
|
|
738
|
+
|
|
739
|
+
# Send OT data to the receiver
|
|
740
|
+
p2p_msgs[other_id] = {
|
|
741
|
+
'delta_ot_data': ot_data_delta,
|
|
742
|
+
'sigma_ot_data': ot_data_sigma,
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
# Update state with alpha values and failed parties
|
|
746
|
+
updated_state = state.copy()
|
|
747
|
+
updated_state['alpha_deltas'] = alpha_deltas
|
|
748
|
+
updated_state['alpha_sigmas'] = alpha_sigmas
|
|
749
|
+
updated_state['all_round2_msgs'] = all_round2_msgs
|
|
750
|
+
updated_state['failed_parties'] = failed_parties
|
|
751
|
+
|
|
752
|
+
return p2p_msgs, updated_state
|
|
753
|
+
|
|
754
|
+
def presign_round4(self, party_id: PartyId, state: Dict[str, Any], p2p_received: Dict[PartyId, Dict[str, Any]]) -> Tuple['Presignature', List[PartyId]]:
|
|
755
|
+
"""
|
|
756
|
+
Round 4: Complete MtA receiver side and compute presignature.
|
|
757
|
+
|
|
758
|
+
Each party completes their role as MtA receiver (getting beta from OT data)
|
|
759
|
+
and computes the final presignature components.
|
|
760
|
+
|
|
761
|
+
Args:
|
|
762
|
+
party_id: This party's identifier
|
|
763
|
+
state: Private state from round 3
|
|
764
|
+
p2p_received: Dictionary {sender_id: p2p_msg} with OT data from round 3
|
|
765
|
+
|
|
766
|
+
Returns:
|
|
767
|
+
Tuple of (Presignature, failed_parties) where:
|
|
768
|
+
- Presignature: The presignature share
|
|
769
|
+
- failed_parties: Always empty (abort happens in round 3 if failures detected)
|
|
770
|
+
|
|
771
|
+
Note:
|
|
772
|
+
Prior to the SecurityAbort fix, failed_parties could be non-empty.
|
|
773
|
+
Now, SecurityAbort is raised in round 3 if any verification fails.
|
|
774
|
+
|
|
775
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
776
|
+
>>> group = ECGroup(secp256k1)
|
|
777
|
+
>>> presign = DKLS23_Presign(group)
|
|
778
|
+
>>> g = group.random(G)
|
|
779
|
+
>>> ts = ThresholdSharing(group)
|
|
780
|
+
>>> x = group.random(ZR)
|
|
781
|
+
>>> x_shares = ts.share(x, 2, 3)
|
|
782
|
+
>>> participants = [1, 2, 3]
|
|
783
|
+
>>> # Full protocol run
|
|
784
|
+
>>> r1, st = {}, {}
|
|
785
|
+
>>> for p in participants:
|
|
786
|
+
... r1[p], st[p] = presign.presign_round1(p, x_shares[p], participants, g)
|
|
787
|
+
>>> r2, p2p_r2 = {}, {}
|
|
788
|
+
>>> for p in participants:
|
|
789
|
+
... r2[p], p2p_r2[p], st[p] = presign.presign_round2(p, st[p], r1)
|
|
790
|
+
>>> recv_r2 = {r: {s: p2p_r2[s][r] for s in participants if s != r} for r in participants}
|
|
791
|
+
>>> p2p_r3 = {}
|
|
792
|
+
>>> for p in participants:
|
|
793
|
+
... p2p_r3[p], st[p] = presign.presign_round3(p, st[p], r2, recv_r2[p])
|
|
794
|
+
>>> recv_r3 = {r: {s: p2p_r3[s][r] for s in participants if s != r} for r in participants}
|
|
795
|
+
>>> presig, failed = presign.presign_round4(1, st[1], recv_r3[1])
|
|
796
|
+
>>> presig.is_valid()
|
|
797
|
+
True
|
|
798
|
+
"""
|
|
799
|
+
k_i = state['k_i']
|
|
800
|
+
gamma_i = state['gamma_i']
|
|
801
|
+
participants = state['participants']
|
|
802
|
+
generator = state['generator']
|
|
803
|
+
mta_results = state['mta_results']
|
|
804
|
+
alpha_deltas = state['alpha_deltas']
|
|
805
|
+
alpha_sigmas = state['alpha_sigmas']
|
|
806
|
+
all_round2_msgs = state['all_round2_msgs']
|
|
807
|
+
failed_parties = state.get('failed_parties', [])
|
|
808
|
+
weighted_key_share = state['weighted_key_share']
|
|
809
|
+
|
|
810
|
+
# Compute delta_i: additive share of k*gamma
|
|
811
|
+
delta_i = k_i * gamma_i # Self-contribution
|
|
812
|
+
|
|
813
|
+
# Add alpha values from sender side
|
|
814
|
+
for other_id, alpha_delta in alpha_deltas.items():
|
|
815
|
+
delta_i = delta_i + alpha_delta
|
|
816
|
+
|
|
817
|
+
# Complete receiver side using OT data
|
|
818
|
+
for other_id in participants:
|
|
819
|
+
if other_id != party_id and other_id in mta_results:
|
|
820
|
+
if other_id in p2p_received:
|
|
821
|
+
p2p_msg = p2p_received[other_id]
|
|
822
|
+
delta_ot_data = p2p_msg.get('delta_ot_data')
|
|
823
|
+
if delta_ot_data is not None:
|
|
824
|
+
mta_receiver = mta_results[other_id]['delta_receiver']
|
|
825
|
+
beta_delta = mta_receiver.receiver_round2(delta_ot_data)
|
|
826
|
+
delta_i = delta_i + beta_delta
|
|
827
|
+
|
|
828
|
+
# Compute sigma_i: additive share of gamma*x
|
|
829
|
+
sigma_i = gamma_i * weighted_key_share # Self-contribution
|
|
830
|
+
|
|
831
|
+
# Add alpha values from sender side
|
|
832
|
+
for other_id, alpha_sigma in alpha_sigmas.items():
|
|
833
|
+
sigma_i = sigma_i + alpha_sigma
|
|
834
|
+
|
|
835
|
+
# Complete receiver side using OT data
|
|
836
|
+
for other_id in participants:
|
|
837
|
+
if other_id != party_id and other_id in mta_results:
|
|
838
|
+
if other_id in p2p_received:
|
|
839
|
+
p2p_msg = p2p_received[other_id]
|
|
840
|
+
sigma_ot_data = p2p_msg.get('sigma_ot_data')
|
|
841
|
+
if sigma_ot_data is not None:
|
|
842
|
+
mta_receiver_sigma = mta_results[other_id]['sigma_receiver']
|
|
843
|
+
beta_sigma = mta_receiver_sigma.receiver_round2(sigma_ot_data)
|
|
844
|
+
sigma_i = sigma_i + beta_sigma
|
|
845
|
+
|
|
846
|
+
# Compute combined Gamma = product of all Gamma_i = g^{sum gamma_i} = g^gamma
|
|
847
|
+
combined_Gamma = None
|
|
848
|
+
for pid in participants:
|
|
849
|
+
Gamma_p = all_round2_msgs[pid]['Gamma_i']
|
|
850
|
+
if combined_Gamma is None:
|
|
851
|
+
combined_Gamma = Gamma_p
|
|
852
|
+
else:
|
|
853
|
+
combined_Gamma = combined_Gamma * Gamma_p
|
|
854
|
+
|
|
855
|
+
# In DKLS23: R = Gamma^{delta^{-1}} = g^{gamma * delta^{-1}} = g^{gamma / (k*gamma)} = g^{1/k}
|
|
856
|
+
# Each party broadcasts their delta_i share
|
|
857
|
+
# For now, we use delta_i locally (in full protocol, parties would share and combine)
|
|
858
|
+
|
|
859
|
+
# The key insight: we have additive shares of delta = k*gamma
|
|
860
|
+
# To compute R = Gamma^{delta^{-1}}, each party computes R_i = Gamma^{delta_i^{-1}}?
|
|
861
|
+
# No, that's wrong. We need to compute delta^{-1} from shares.
|
|
862
|
+
|
|
863
|
+
# Simpler approach: broadcast delta_i and combine
|
|
864
|
+
# For this implementation, we compute R locally knowing delta_i
|
|
865
|
+
# In reality, parties would use secure inversion or additional protocols
|
|
866
|
+
|
|
867
|
+
# For 2-party case or when all parties participate, we can compute delta directly
|
|
868
|
+
# delta = sum(delta_i) over all participants
|
|
869
|
+
# But we only have our own delta_i here!
|
|
870
|
+
|
|
871
|
+
# We need to receive delta_i from all parties. For now, we'll compute R differently:
|
|
872
|
+
# R = product of R_i = g^{sum k_i} = g^k (not g^{1/k})
|
|
873
|
+
# Then in signing we use: s_i = delta_i^{-1} * (e * gamma_i + r * sigma_i)
|
|
874
|
+
# where sigma_i = chi_i * gamma_i (share of k*x*gamma)
|
|
875
|
+
|
|
876
|
+
# Actually, let's follow the simpler GG-style approach:
|
|
877
|
+
# R = g^k (product of g^{k_i})
|
|
878
|
+
# Then s = k^{-1}(e + rx) needs shares of k^{-1}
|
|
879
|
+
#
|
|
880
|
+
# DKLS23 avoids computing k^{-1} by using:
|
|
881
|
+
# - delta = k*gamma
|
|
882
|
+
# - sigma = delta*x = k*gamma*x
|
|
883
|
+
# - R = Gamma^{delta^{-1}} = g^{1/k}
|
|
884
|
+
# - s_i = e * delta_i + r * sigma_i (shares of e*delta + r*sigma = e*k*gamma + r*k*gamma*x = k*gamma*(e + rx))
|
|
885
|
+
# - Final: s = sum(s_i) / gamma = k*gamma*(e+rx) / gamma = k*(e+rx)
|
|
886
|
+
# Wait, that's still k*(e+rx) not k^{-1}*(e+rx)
|
|
887
|
+
|
|
888
|
+
# Let me reconsider. In DKLS23:
|
|
889
|
+
# - R = g^{1/k} (computed as Gamma^{delta^{-1}})
|
|
890
|
+
# - r = x-coordinate of R
|
|
891
|
+
# - sigma = k*x (shares via MtA)
|
|
892
|
+
# - s_i = k_i * e + sigma_i = k_i * e + (k*x)_i <- this is wrong interpretation
|
|
893
|
+
|
|
894
|
+
# Actually the signature formula is:
|
|
895
|
+
# s = k^{-1} * (e + r*x)
|
|
896
|
+
# If R = g^{1/k}, then we need to express s in terms of what we have
|
|
897
|
+
#
|
|
898
|
+
# What we have after MtA:
|
|
899
|
+
# - k_i : additive shares of k (just random, sum to k)
|
|
900
|
+
# - delta_i : additive shares of delta = k*gamma
|
|
901
|
+
# - chi_i : additive shares of chi = k*x
|
|
902
|
+
#
|
|
903
|
+
# For signature: s = k^{-1} * (e + r*x)
|
|
904
|
+
# Rewrite: s = (e + r*x) / k
|
|
905
|
+
#
|
|
906
|
+
# We can compute this as:
|
|
907
|
+
# s = (e + r*x) / k = e/k + r*x/k
|
|
908
|
+
#
|
|
909
|
+
# Note: chi = k*x, so x = chi/k
|
|
910
|
+
# Thus: s = e/k + r*chi/k^2 <- messy
|
|
911
|
+
#
|
|
912
|
+
# Alternative: Use delta and gamma
|
|
913
|
+
# s = (e + r*x) / k
|
|
914
|
+
# = (e + r*x) * gamma / (k*gamma)
|
|
915
|
+
# = (e + r*x) * gamma / delta
|
|
916
|
+
# = (e*gamma + r*x*gamma) / delta
|
|
917
|
+
#
|
|
918
|
+
# We need shares of:
|
|
919
|
+
# - gamma: each party has gamma_i, sum = gamma
|
|
920
|
+
# - x*gamma: need MtA for x_i * gamma_j
|
|
921
|
+
# - delta: we have shares delta_i
|
|
922
|
+
#
|
|
923
|
+
# Then: s_i = (e*gamma_i + r*(x*gamma)_i) * delta^{-1}
|
|
924
|
+
#
|
|
925
|
+
# But we need delta^{-1} computed from shares... that's the tricky part.
|
|
926
|
+
#
|
|
927
|
+
# DKLS23 solution: reveal delta, then everyone computes delta^{-1}
|
|
928
|
+
# This is secure because delta = k*gamma is uniformly random (gamma is random blinding)
|
|
929
|
+
|
|
930
|
+
# For this implementation, let's broadcast delta_i in round 3 and compute delta
|
|
931
|
+
# Then R = Gamma^{delta^{-1}}, and signature uses delta^{-1}
|
|
932
|
+
|
|
933
|
+
# Since we're in round3 and need delta from all parties, we'll need to
|
|
934
|
+
# include delta_i in the presignature and do a final combination step
|
|
935
|
+
|
|
936
|
+
# For now, let's compute R the simple way and adjust the signing formula
|
|
937
|
+
# R = g^k, then we need s shares that sum to k^{-1}(e+rx)
|
|
938
|
+
#
|
|
939
|
+
# Simpler: Use the fact that we have chi = k*x
|
|
940
|
+
# s = k^{-1}(e + rx) = k^{-1}*e + r*x*k^{-1}
|
|
941
|
+
#
|
|
942
|
+
# If we had shares of k^{-1}, we could compute s_i = k^{-1}_i * e + r * (k^{-1}*x)_i
|
|
943
|
+
# But computing shares of k^{-1} from shares of k requires secure inversion.
|
|
944
|
+
#
|
|
945
|
+
# DKLS23's clever trick: Use delta and gamma to avoid explicit k^{-1}
|
|
946
|
+
#
|
|
947
|
+
# Let's implement it properly:
|
|
948
|
+
# 1. R = Gamma^{delta^{-1}} where delta = sum(delta_i) is revealed
|
|
949
|
+
# 2. sigma_i = gamma_i * chi_i + sum(MtA for gamma_j * chi_i) = share of gamma*chi = gamma*k*x
|
|
950
|
+
# 3. s_i = (e * gamma_i + r * sigma_i / chi?)
|
|
951
|
+
#
|
|
952
|
+
# This is getting complicated. Let me use a simpler approach that works:
|
|
953
|
+
#
|
|
954
|
+
# Standard threshold ECDSA approach:
|
|
955
|
+
# - k_i: additive shares of k, R = g^k
|
|
956
|
+
# - chi_i: additive shares of k*x
|
|
957
|
+
# - Each party broadcasts delta_i = k_i*gamma_i + MtA terms (share of k*gamma = delta)
|
|
958
|
+
# - Parties compute delta = sum(delta_i) and delta^{-1}
|
|
959
|
+
# - R = Gamma^{delta^{-1}} = g^{gamma/delta} = g^{1/k}
|
|
960
|
+
# - s_i = m * w_i + r * chi_i * w where w = delta^{-1} and w_i is distributed somehow
|
|
961
|
+
#
|
|
962
|
+
# Actually, let's use the simple approach from GG20 adapted:
|
|
963
|
+
# - R = g^k (compute as product of R_i = g^{k_i})
|
|
964
|
+
# - chi_i = share of k*x
|
|
965
|
+
# - sigma_i = k_i * w + chi_i * w where w = k^{-1} computed using delta and gamma
|
|
966
|
+
#
|
|
967
|
+
# The key insight from DKLS23: reveal delta = k*gamma, compute delta^{-1}
|
|
968
|
+
# Then k^{-1} = delta^{-1} * gamma
|
|
969
|
+
# s = k^{-1}(e + rx) = delta^{-1} * gamma * (e + rx)
|
|
970
|
+
# = delta^{-1} * (e*gamma + r*gamma*x)
|
|
971
|
+
#
|
|
972
|
+
# Shares of e*gamma: each party has gamma_i, can compute e*gamma_i
|
|
973
|
+
# Shares of gamma*x: need MtA between gamma_i and x_j
|
|
974
|
+
#
|
|
975
|
+
# Currently we have chi_i = shares of k*x, not gamma*x
|
|
976
|
+
# We need to modify the protocol...
|
|
977
|
+
#
|
|
978
|
+
# OR: use the following identity:
|
|
979
|
+
# s = k^{-1}(e + rx)
|
|
980
|
+
# Let's verify k*s = e + rx
|
|
981
|
+
# This means: sum(s_i) * sum(k_i) = e + r*sum(x_i)
|
|
982
|
+
#
|
|
983
|
+
# We can set s_i such that sum(lambda_i * s_i) = k^{-1}(e + rx)
|
|
984
|
+
# where lambda_i are Lagrange coefficients
|
|
985
|
+
#
|
|
986
|
+
# From chi = k*x (with shares chi_i), we have sum(chi_i) = k*x
|
|
987
|
+
# Signature share: s_i = (e + r * chi_i / k_i)? No, that doesn't work with addition.
|
|
988
|
+
#
|
|
989
|
+
# Let me try a different approach. We need the signature to verify, which means:
|
|
990
|
+
# Given R = g^k and s, verify: g^{s^{-1}*e} * pk^{s^{-1}*r} = R
|
|
991
|
+
# i.e., g^{(e + rx)/s} = g^k
|
|
992
|
+
# So we need s = (e + rx)/k = k^{-1}(e + rx)
|
|
993
|
+
#
|
|
994
|
+
# Our current formula: s_i = k_i * e + r * chi_i
|
|
995
|
+
# sum(lambda_i * s_i) = sum(lambda_i * k_i * e) + r * sum(lambda_i * chi_i)
|
|
996
|
+
# = k * e + r * k * x (using Lagrange reconstruction)
|
|
997
|
+
# = k * (e + r*x)
|
|
998
|
+
#
|
|
999
|
+
# So we're computing k*(e+rx) but we need k^{-1}*(e+rx)!
|
|
1000
|
+
#
|
|
1001
|
+
# Fix: s_i should be k_i^{-1} * e + r * chi_i * k^{-2}? No, that doesn't work.
|
|
1002
|
+
#
|
|
1003
|
+
# The correct fix for threshold ECDSA:
|
|
1004
|
+
# Use additive shares where the Lagrange reconstruction gives k^{-1}
|
|
1005
|
+
#
|
|
1006
|
+
# This requires computing shares of k^{-1} from shares of k using:
|
|
1007
|
+
# - Reveal delta = k*gamma (safe because gamma is random blinding)
|
|
1008
|
+
# - Then k^{-1} = gamma * delta^{-1}
|
|
1009
|
+
# - Shares of k^{-1}: k^{-1}_i = gamma_i * delta^{-1} (where delta^{-1} is public after revealing delta)
|
|
1010
|
+
#
|
|
1011
|
+
# Now: s_i = k^{-1}_i * e + r * chi_i * k^{-1}
|
|
1012
|
+
# But chi_i is share of k*x, and k^{-1}_i is share of k^{-1}
|
|
1013
|
+
# We need share of k^{-1} * k * x = x
|
|
1014
|
+
# But that's just x_i!
|
|
1015
|
+
#
|
|
1016
|
+
# So: s_i = k^{-1}_i * e + r * x_i * k^{-1}? No wait...
|
|
1017
|
+
#
|
|
1018
|
+
# Let me be more careful. We have:
|
|
1019
|
+
# - k^{-1} = gamma * delta^{-1} (delta^{-1} is public)
|
|
1020
|
+
# - k^{-1}_i = gamma_i * delta^{-1}
|
|
1021
|
+
#
|
|
1022
|
+
# s = k^{-1}(e + rx) = k^{-1}*e + k^{-1}*r*x
|
|
1023
|
+
# = delta^{-1} * gamma * e + delta^{-1} * gamma * r * x
|
|
1024
|
+
# = delta^{-1} * (gamma * e + gamma * r * x)
|
|
1025
|
+
# = delta^{-1} * (e*gamma + r * (gamma * x))
|
|
1026
|
+
#
|
|
1027
|
+
# Shares:
|
|
1028
|
+
# - (e*gamma)_i = e * gamma_i (each party computes locally)
|
|
1029
|
+
# - (gamma*x)_i = ? Need MtA for gamma_i * x_j
|
|
1030
|
+
#
|
|
1031
|
+
# Currently we compute chi = k*x via MtA
|
|
1032
|
+
# We also need to compute gamma*x via MtA (or store gamma and x shares)
|
|
1033
|
+
#
|
|
1034
|
+
# Simpler: sigma_i = gamma_i * x_i + sum(MtA for gamma_i * x_j and gamma_j * x_i)
|
|
1035
|
+
# = share of gamma*x
|
|
1036
|
+
#
|
|
1037
|
+
# Then: s_i = delta^{-1} * (e * gamma_i + r * sigma_i)
|
|
1038
|
+
#
|
|
1039
|
+
# Since delta^{-1} is a scalar (not shared), this gives correct s when summed!
|
|
1040
|
+
#
|
|
1041
|
+
# Let's implement this. We need:
|
|
1042
|
+
# 1. Keep delta_i computation (shares of k*gamma)
|
|
1043
|
+
# 2. Add sigma_i computation (shares of gamma*x via MtA)
|
|
1044
|
+
# 3. Reveal sum of delta_i to get delta
|
|
1045
|
+
# 4. R = Gamma^{delta^{-1}}
|
|
1046
|
+
# 5. Signature: s_i = delta^{-1} * (e * gamma_i + r * sigma_i)
|
|
1047
|
+
|
|
1048
|
+
# For now, let me restructure. We need gamma_i stored, and we need
|
|
1049
|
+
# to compute sigma (gamma*x) instead of or in addition to chi (k*x).
|
|
1050
|
+
#
|
|
1051
|
+
# Actually, looking at our current code, we're computing chi = k*x.
|
|
1052
|
+
# We should instead compute sigma = gamma*x.
|
|
1053
|
+
# Then the signing formula becomes:
|
|
1054
|
+
# s_i = delta^{-1} * (e * gamma_i + r * sigma_i)
|
|
1055
|
+
#
|
|
1056
|
+
# Let's update the Presignature to store delta_i and gamma_i
|
|
1057
|
+
|
|
1058
|
+
# Compute combined R = product of all R_i = g^{sum k_i} for now
|
|
1059
|
+
# (We'll fix the formula to use delta properly)
|
|
1060
|
+
combined_R = None
|
|
1061
|
+
for pid in participants:
|
|
1062
|
+
R_p = all_round2_msgs[pid]['R_i']
|
|
1063
|
+
if combined_R is None:
|
|
1064
|
+
combined_R = R_p
|
|
1065
|
+
else:
|
|
1066
|
+
combined_R = combined_R * R_p
|
|
1067
|
+
|
|
1068
|
+
# R = g^k for now (will be corrected in signing with delta^{-1})
|
|
1069
|
+
R = combined_R
|
|
1070
|
+
|
|
1071
|
+
# Compute r = R.x mod q
|
|
1072
|
+
r = self.group.zr(R)
|
|
1073
|
+
|
|
1074
|
+
presignature = Presignature(
|
|
1075
|
+
party_id=party_id,
|
|
1076
|
+
R=R,
|
|
1077
|
+
r=r,
|
|
1078
|
+
k_share=k_i,
|
|
1079
|
+
chi_share=sigma_i, # This is gamma*x share for signature computation
|
|
1080
|
+
participants=participants,
|
|
1081
|
+
gamma_i=gamma_i,
|
|
1082
|
+
delta_i=delta_i
|
|
1083
|
+
)
|
|
1084
|
+
return (presignature, failed_parties)
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
if __name__ == "__main__":
|
|
1088
|
+
import doctest
|
|
1089
|
+
doctest.testmod()
|