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
charm/toolbox/mta.py
ADDED
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Multiplicative-to-Additive (MtA) Share Conversion for DKLS23
|
|
3
|
+
|
|
4
|
+
| From: "Threshold ECDSA from ECDSA Assumptions: The Multiparty Case"
|
|
5
|
+
| By: Jack Doerner, Yashvanth Kondi, Eysa Lee, abhi shelat
|
|
6
|
+
| Published: IEEE S&P 2019
|
|
7
|
+
| URL: https://eprint.iacr.org/2019/523
|
|
8
|
+
|
|
|
9
|
+
| Also implements MtAwc (MtA with check) from:
|
|
10
|
+
| "Two-Round Threshold ECDSA from ECDSA Assumptions" (DKLS23)
|
|
11
|
+
| By: Jack Doerner, Yashvanth Kondi, Eysa Lee, abhi shelat
|
|
12
|
+
| Published: IEEE S&P 2023
|
|
13
|
+
| URL: https://eprint.iacr.org/2023/765
|
|
14
|
+
|
|
15
|
+
* type: share conversion
|
|
16
|
+
* setting: Elliptic Curve DDH-hard group
|
|
17
|
+
* assumption: DDH + OT security
|
|
18
|
+
|
|
19
|
+
MtA converts multiplicative shares (a, b) where two parties hold a and b
|
|
20
|
+
to additive shares (alpha, beta) such that a*b = alpha + beta (mod q).
|
|
21
|
+
Neither party learns the other's share.
|
|
22
|
+
|
|
23
|
+
:Authors: Elton de Souza
|
|
24
|
+
:Date: 01/2026
|
|
25
|
+
'''
|
|
26
|
+
|
|
27
|
+
from typing import Dict, List, Tuple, Optional, Any, Union
|
|
28
|
+
|
|
29
|
+
from charm.toolbox.ecgroup import ECGroup, ZR, G
|
|
30
|
+
from charm.toolbox.eccurve import secp256k1
|
|
31
|
+
from charm.toolbox.securerandom import SecureRandomFactory
|
|
32
|
+
from charm.toolbox.ot.base_ot import SimpleOT
|
|
33
|
+
from charm.toolbox.mpc_utils import (
|
|
34
|
+
int_to_bytes,
|
|
35
|
+
bytes_to_int,
|
|
36
|
+
bit_decompose,
|
|
37
|
+
bits_to_int,
|
|
38
|
+
PedersenCommitment,
|
|
39
|
+
)
|
|
40
|
+
import struct
|
|
41
|
+
import hashlib
|
|
42
|
+
import logging
|
|
43
|
+
|
|
44
|
+
# Type aliases for charm-crypto types
|
|
45
|
+
ZRElement = Any # Scalar field element
|
|
46
|
+
GElement = Any # Group/curve point element
|
|
47
|
+
ECGroupType = Any # ECGroup instance
|
|
48
|
+
|
|
49
|
+
# Module logger
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def hash_to_field(group: ECGroupType, *args: Any) -> ZRElement:
|
|
54
|
+
"""
|
|
55
|
+
Hash multiple values to a field element with domain separation.
|
|
56
|
+
|
|
57
|
+
Uses group.hash() for proper domain separation and automatic
|
|
58
|
+
serialization of different types.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
group : ECGroup
|
|
63
|
+
The elliptic curve group
|
|
64
|
+
*args : various
|
|
65
|
+
Values to hash
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
ZR element
|
|
70
|
+
Hash output as field element
|
|
71
|
+
"""
|
|
72
|
+
return group.hash((b"MTA_FIELD:",) + args, target_type=ZR)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CorrelatedOT:
|
|
76
|
+
"""
|
|
77
|
+
Correlated Oblivious Transfer for MtA.
|
|
78
|
+
|
|
79
|
+
Generates correlated random values for OT-based MtA.
|
|
80
|
+
For each bit of the sender's input, generates correlation pairs.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(self, groupObj):
|
|
84
|
+
"""
|
|
85
|
+
Initialize CorrelatedOT with an elliptic curve group.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
groupObj : ECGroup
|
|
90
|
+
An elliptic curve group object
|
|
91
|
+
"""
|
|
92
|
+
self.group = groupObj
|
|
93
|
+
self.order = int(groupObj.order())
|
|
94
|
+
self.rand = SecureRandomFactory.getInstance()
|
|
95
|
+
# Bit length based on group order
|
|
96
|
+
self.bit_length = self.order.bit_length()
|
|
97
|
+
|
|
98
|
+
def generate_correlation(self, delta):
|
|
99
|
+
"""
|
|
100
|
+
Generate correlated pair (t0, t1) where t1 = t0 + delta.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
delta : int
|
|
105
|
+
The correlation offset
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
tuple
|
|
110
|
+
(t0, t1) where t1 = t0 + delta (mod order)
|
|
111
|
+
"""
|
|
112
|
+
t0 = bytes_to_int(self.rand.getRandomBytes(32)) % self.order
|
|
113
|
+
t1 = (t0 + delta) % self.order
|
|
114
|
+
return (t0, t1)
|
|
115
|
+
|
|
116
|
+
def generate_batch_correlations(self, deltas):
|
|
117
|
+
"""
|
|
118
|
+
Generate batch of correlated pairs.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
deltas : list of int
|
|
123
|
+
List of correlation offsets
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
list of tuples
|
|
128
|
+
List of (t0, t1) pairs
|
|
129
|
+
"""
|
|
130
|
+
return [self.generate_correlation(d) for d in deltas]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MtA:
|
|
134
|
+
"""
|
|
135
|
+
Multiplicative-to-Additive share conversion using OT.
|
|
136
|
+
|
|
137
|
+
Converts multiplicative shares (a, b) where parties hold a and b
|
|
138
|
+
to additive shares (alpha, beta) where a*b = alpha + beta (mod q).
|
|
139
|
+
|
|
140
|
+
Curve Agnostic
|
|
141
|
+
--------------
|
|
142
|
+
This implementation supports any elliptic curve group that is DDH-hard.
|
|
143
|
+
The curve is specified via the groupObj parameter.
|
|
144
|
+
|
|
145
|
+
The protocol works as follows:
|
|
146
|
+
1. Sender (holding a) decomposes a into bits
|
|
147
|
+
2. For each bit position i, run correlated OT with correlation 2^i * b
|
|
148
|
+
3. Receiver (holding b) chooses based on sender's bits
|
|
149
|
+
4. Parties compute their additive shares from OT outputs
|
|
150
|
+
|
|
151
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
152
|
+
>>> group = ECGroup(secp256k1)
|
|
153
|
+
>>> # Create separate instances for Alice (sender) and Bob (receiver)
|
|
154
|
+
>>> alice_mta = MtA(group)
|
|
155
|
+
>>> bob_mta = MtA(group)
|
|
156
|
+
>>> # Alice has share a, Bob has share b
|
|
157
|
+
>>> a = group.random(ZR)
|
|
158
|
+
>>> b = group.random(ZR)
|
|
159
|
+
>>> # Convert to additive shares using the protocol with real OT
|
|
160
|
+
>>> sender_msg = alice_mta.sender_round1(a)
|
|
161
|
+
>>> receiver_msg, _ = bob_mta.receiver_round1(b, sender_msg)
|
|
162
|
+
>>> alpha, ot_data = alice_mta.sender_round2(receiver_msg)
|
|
163
|
+
>>> beta = bob_mta.receiver_round2(ot_data)
|
|
164
|
+
>>> # Verify: a*b = alpha + beta (mod q)
|
|
165
|
+
>>> product = a * b
|
|
166
|
+
>>> additive_sum = alpha + beta
|
|
167
|
+
>>> product == additive_sum
|
|
168
|
+
True
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(self, groupObj: ECGroupType) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Initialize MtA with an elliptic curve group.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
groupObj : ECGroup
|
|
178
|
+
An elliptic curve group object from charm.toolbox.ecgroup
|
|
179
|
+
|
|
180
|
+
Raises
|
|
181
|
+
------
|
|
182
|
+
ValueError
|
|
183
|
+
If groupObj is None
|
|
184
|
+
"""
|
|
185
|
+
if groupObj is None:
|
|
186
|
+
raise ValueError("groupObj cannot be None")
|
|
187
|
+
self.group = groupObj
|
|
188
|
+
self.order = int(groupObj.order())
|
|
189
|
+
self.rand = SecureRandomFactory.getInstance()
|
|
190
|
+
self.bit_length = self.order.bit_length()
|
|
191
|
+
|
|
192
|
+
# State variables
|
|
193
|
+
self._a = None
|
|
194
|
+
self._alpha = None
|
|
195
|
+
|
|
196
|
+
def sender_round1(self, a: ZRElement) -> Dict[str, Any]:
|
|
197
|
+
"""
|
|
198
|
+
Sender (holding a) generates first message.
|
|
199
|
+
|
|
200
|
+
Sender samples random alpha and prepares OT messages such that
|
|
201
|
+
receiver can learn beta = a*b - alpha. Uses real SimpleOT for security.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
a : ZR element
|
|
206
|
+
Sender's multiplicative share
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
dict
|
|
211
|
+
OT setup parameters containing:
|
|
212
|
+
- 'ot_params': list of OT sender parameters (one per bit position)
|
|
213
|
+
- 'adjustment': integer for receiver to compute beta
|
|
214
|
+
"""
|
|
215
|
+
self._a = a
|
|
216
|
+
a_int = int(a) % self.order
|
|
217
|
+
|
|
218
|
+
# Sample random alpha
|
|
219
|
+
alpha_int = bytes_to_int(self.rand.getRandomBytes(32)) % self.order
|
|
220
|
+
self._alpha = alpha_int
|
|
221
|
+
|
|
222
|
+
# OT-based MtA protocol:
|
|
223
|
+
# Goal: alpha + beta = a*b, where sender gets alpha (random), receiver gets beta
|
|
224
|
+
#
|
|
225
|
+
# Receiver has b = sum_i b_i * 2^i
|
|
226
|
+
# For each bit position i, sender prepares two messages:
|
|
227
|
+
# m0_i = r_i (receiver gets this if b_i = 0)
|
|
228
|
+
# m1_i = r_i + a * 2^i (receiver gets this if b_i = 1)
|
|
229
|
+
#
|
|
230
|
+
# After OT, receiver has: sum_i selected_i = sum_i (r_i + b_i * a * 2^i) = r_sum + a*b
|
|
231
|
+
#
|
|
232
|
+
# To get beta = a*b - alpha:
|
|
233
|
+
# beta = sum(selected) - (r_sum + alpha)
|
|
234
|
+
# So sender sends: adjustment = r_sum + alpha
|
|
235
|
+
|
|
236
|
+
# Store OT senders and messages for the transfer phase
|
|
237
|
+
self._ot_senders = []
|
|
238
|
+
self._ot_raw_messages = []
|
|
239
|
+
ot_params_list = []
|
|
240
|
+
r_sum = 0
|
|
241
|
+
|
|
242
|
+
for i in range(self.bit_length):
|
|
243
|
+
# Random mask for this position
|
|
244
|
+
r_i = bytes_to_int(self.rand.getRandomBytes(32)) % self.order
|
|
245
|
+
r_sum = (r_sum + r_i) % self.order
|
|
246
|
+
|
|
247
|
+
# m0 = r_i (receiver gets this if b_i = 0)
|
|
248
|
+
# m1 = r_i + a * 2^i (receiver gets this if b_i = 1)
|
|
249
|
+
power_of_two = (1 << i) % self.order
|
|
250
|
+
m0 = r_i
|
|
251
|
+
m1 = (r_i + a_int * power_of_two) % self.order
|
|
252
|
+
|
|
253
|
+
# Create OT sender instance and setup
|
|
254
|
+
ot_sender = SimpleOT(self.group)
|
|
255
|
+
sender_params = ot_sender.sender_setup()
|
|
256
|
+
|
|
257
|
+
self._ot_senders.append(ot_sender)
|
|
258
|
+
self._ot_raw_messages.append((m0, m1))
|
|
259
|
+
ot_params_list.append(sender_params)
|
|
260
|
+
|
|
261
|
+
# Sender sends r_sum + alpha so receiver can compute beta = sum(selected) - (r_sum + alpha)
|
|
262
|
+
adjustment = (r_sum + alpha_int) % self.order
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
'ot_params': ot_params_list,
|
|
266
|
+
'adjustment': adjustment,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
def receiver_round1(self, b: ZRElement, sender_msg: Dict[str, Any]) -> Tuple[Dict[str, Any], None]:
|
|
270
|
+
"""
|
|
271
|
+
Receiver (holding b) selects OT messages based on bits of b.
|
|
272
|
+
|
|
273
|
+
Uses real SimpleOT: for each bit b_i, receiver only learns m_{b_i}.
|
|
274
|
+
The receiver NEVER sees both m0 and m1.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
b : ZR element
|
|
279
|
+
Receiver's multiplicative share
|
|
280
|
+
sender_msg : dict
|
|
281
|
+
Message from sender_round1
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
tuple (dict, None)
|
|
286
|
+
A tuple containing:
|
|
287
|
+
- dict: Receiver parameters with 'ot_responses' list of OT receiver responses
|
|
288
|
+
- None: Placeholder for beta (computed in receiver_round2)
|
|
289
|
+
"""
|
|
290
|
+
ot_params_list = sender_msg['ot_params']
|
|
291
|
+
self._adjustment = sender_msg['adjustment']
|
|
292
|
+
|
|
293
|
+
b_int = int(b) % self.order
|
|
294
|
+
bits_b = bit_decompose(b_int, self.order, len(ot_params_list))
|
|
295
|
+
|
|
296
|
+
# Use real OT to select messages based on bits of b
|
|
297
|
+
# Receiver only learns m_{b_i} for each position - never both messages
|
|
298
|
+
self._ot_receivers = []
|
|
299
|
+
self._ot_receiver_states = []
|
|
300
|
+
ot_responses = []
|
|
301
|
+
|
|
302
|
+
for i, bit in enumerate(bits_b):
|
|
303
|
+
ot_receiver = SimpleOT(self.group)
|
|
304
|
+
receiver_response, receiver_state = ot_receiver.receiver_choose(ot_params_list[i], bit)
|
|
305
|
+
|
|
306
|
+
self._ot_receivers.append(ot_receiver)
|
|
307
|
+
self._ot_receiver_states.append(receiver_state)
|
|
308
|
+
ot_responses.append(receiver_response)
|
|
309
|
+
|
|
310
|
+
# Store bits for compatibility with old interface
|
|
311
|
+
self._bits_b = bits_b
|
|
312
|
+
|
|
313
|
+
return {'ot_responses': ot_responses}, None
|
|
314
|
+
|
|
315
|
+
def sender_round2(self, receiver_msg: Dict[str, Any]) -> Tuple[ZRElement, Dict[str, Any]]:
|
|
316
|
+
"""
|
|
317
|
+
Sender processes receiver's OT responses and returns alpha.
|
|
318
|
+
|
|
319
|
+
Parameters
|
|
320
|
+
----------
|
|
321
|
+
receiver_msg : dict
|
|
322
|
+
Message from receiver_round1 containing OT responses
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
tuple (ZR element, dict)
|
|
327
|
+
A tuple containing:
|
|
328
|
+
- ZR element: Sender's additive share alpha
|
|
329
|
+
- dict: OT data with 'ot_ciphertexts' list for receiver to retrieve
|
|
330
|
+
"""
|
|
331
|
+
ot_responses = receiver_msg['ot_responses']
|
|
332
|
+
ot_ciphertexts = []
|
|
333
|
+
|
|
334
|
+
# Complete OT transfer for each bit position
|
|
335
|
+
for i, ot_sender in enumerate(self._ot_senders):
|
|
336
|
+
m0, m1 = self._ot_raw_messages[i]
|
|
337
|
+
# Convert integers to bytes for OT encryption
|
|
338
|
+
m0_bytes = int_to_bytes(m0, 32)
|
|
339
|
+
m1_bytes = int_to_bytes(m1, 32)
|
|
340
|
+
ciphertexts = ot_sender.sender_transfer(ot_responses[i], m0_bytes, m1_bytes)
|
|
341
|
+
ot_ciphertexts.append(ciphertexts)
|
|
342
|
+
|
|
343
|
+
alpha = self.group.init(ZR, self._alpha)
|
|
344
|
+
return alpha, {'ot_ciphertexts': ot_ciphertexts}
|
|
345
|
+
|
|
346
|
+
def receiver_round2(self, sender_round2_msg: Dict[str, Any]) -> ZRElement:
|
|
347
|
+
"""
|
|
348
|
+
Receiver retrieves selected OT messages and computes beta.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
sender_round2_msg : dict
|
|
353
|
+
Message from sender_round2 containing OT ciphertexts
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
ZR element
|
|
358
|
+
Receiver's additive share beta such that a*b = alpha + beta (mod q)
|
|
359
|
+
"""
|
|
360
|
+
ot_ciphertexts = sender_round2_msg['ot_ciphertexts']
|
|
361
|
+
|
|
362
|
+
# Retrieve selected messages using OT - receiver only gets m_{b_i}
|
|
363
|
+
selected_sum = 0
|
|
364
|
+
for i, ot_receiver in enumerate(self._ot_receivers):
|
|
365
|
+
selected_bytes = ot_receiver.receiver_retrieve(
|
|
366
|
+
ot_ciphertexts[i],
|
|
367
|
+
self._ot_receiver_states[i]
|
|
368
|
+
)
|
|
369
|
+
selected = bytes_to_int(selected_bytes)
|
|
370
|
+
selected_sum = (selected_sum + selected) % self.order
|
|
371
|
+
|
|
372
|
+
# beta = sum(selected) - adjustment = (r_sum + a*b) - (r_sum + alpha) = a*b - alpha
|
|
373
|
+
beta_int = (selected_sum - self._adjustment) % self.order
|
|
374
|
+
self._beta = self.group.init(ZR, beta_int)
|
|
375
|
+
|
|
376
|
+
return self._beta
|
|
377
|
+
|
|
378
|
+
def receiver_complete(self, sender_bits: List[int]) -> ZRElement:
|
|
379
|
+
"""
|
|
380
|
+
Receiver returns their additive share beta (already computed).
|
|
381
|
+
|
|
382
|
+
Parameters
|
|
383
|
+
----------
|
|
384
|
+
sender_bits : list of int
|
|
385
|
+
Sender's bit decomposition (unused in correct protocol)
|
|
386
|
+
|
|
387
|
+
Returns
|
|
388
|
+
-------
|
|
389
|
+
ZR element
|
|
390
|
+
Receiver's additive share beta
|
|
391
|
+
"""
|
|
392
|
+
# Beta was already computed in receiver_round1
|
|
393
|
+
return self._beta
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class MtAwc:
|
|
398
|
+
"""
|
|
399
|
+
MtA with check - includes ZK proof that conversion is correct.
|
|
400
|
+
|
|
401
|
+
Used for malicious security. Adds commitment and proof phases
|
|
402
|
+
to verify that parties performed MtA correctly.
|
|
403
|
+
|
|
404
|
+
The protocol adds:
|
|
405
|
+
1. Commitment phase: parties commit to their shares
|
|
406
|
+
2. Proof phase: parties prove correctness of OT selections
|
|
407
|
+
3. Verification: parties verify each other's proofs
|
|
408
|
+
|
|
409
|
+
>>> from charm.toolbox.eccurve import secp256k1
|
|
410
|
+
>>> group = ECGroup(secp256k1)
|
|
411
|
+
>>> mta_wc = MtAwc(group)
|
|
412
|
+
>>> # Alice has share a, Bob has share b
|
|
413
|
+
>>> a = group.random(ZR)
|
|
414
|
+
>>> b = group.random(ZR)
|
|
415
|
+
>>> # Run MtA with correctness check
|
|
416
|
+
>>> sender_commit = mta_wc.sender_commit(a)
|
|
417
|
+
>>> receiver_commit = mta_wc.receiver_commit(b)
|
|
418
|
+
>>> # Exchange commitments and run MtA
|
|
419
|
+
>>> sender_msg = mta_wc.sender_round1(a, receiver_commit)
|
|
420
|
+
>>> receiver_msg, _ = mta_wc.receiver_round1(b, sender_commit, sender_msg)
|
|
421
|
+
>>> alpha, sender_proof = mta_wc.sender_round2(receiver_msg)
|
|
422
|
+
>>> beta, valid = mta_wc.receiver_verify(sender_proof)
|
|
423
|
+
>>> valid
|
|
424
|
+
True
|
|
425
|
+
>>> # Verify: a*b = alpha + beta (mod q)
|
|
426
|
+
>>> product = a * b
|
|
427
|
+
>>> additive_sum = alpha + beta
|
|
428
|
+
>>> product == additive_sum
|
|
429
|
+
True
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
def __init__(self, groupObj: ECGroupType) -> None:
|
|
433
|
+
"""
|
|
434
|
+
Initialize MtAwc with an elliptic curve group.
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
groupObj : ECGroup
|
|
439
|
+
An elliptic curve group object from charm.toolbox.ecgroup
|
|
440
|
+
|
|
441
|
+
Raises
|
|
442
|
+
------
|
|
443
|
+
ValueError
|
|
444
|
+
If groupObj is None
|
|
445
|
+
"""
|
|
446
|
+
if groupObj is None:
|
|
447
|
+
raise ValueError("groupObj cannot be None")
|
|
448
|
+
self.group = groupObj
|
|
449
|
+
self.order = int(groupObj.order())
|
|
450
|
+
self.rand = SecureRandomFactory.getInstance()
|
|
451
|
+
self.bit_length = self.order.bit_length()
|
|
452
|
+
self.mta = MtA(groupObj)
|
|
453
|
+
|
|
454
|
+
# Use centralized PedersenCommitment
|
|
455
|
+
self._pedersen = PedersenCommitment(groupObj)
|
|
456
|
+
self._pedersen.setup()
|
|
457
|
+
self._g = self._pedersen.g
|
|
458
|
+
self._h = self._pedersen.h
|
|
459
|
+
|
|
460
|
+
# State
|
|
461
|
+
self._a = None
|
|
462
|
+
self._b = None
|
|
463
|
+
self._commitment_randomness = None
|
|
464
|
+
self._sender_commit = None
|
|
465
|
+
self._receiver_commit = None
|
|
466
|
+
self._sender_bit_proof = None
|
|
467
|
+
|
|
468
|
+
def _pedersen_commit(self, value: Union[ZRElement, int], randomness: Optional[ZRElement] = None) -> Tuple[GElement, ZRElement]:
|
|
469
|
+
"""
|
|
470
|
+
Create Pedersen commitment: C = g^value * h^randomness.
|
|
471
|
+
|
|
472
|
+
Delegates to the centralized PedersenCommitment class.
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
value : ZR element or int
|
|
477
|
+
Value to commit to
|
|
478
|
+
randomness : ZR element, optional
|
|
479
|
+
Randomness for commitment (generated if not provided)
|
|
480
|
+
|
|
481
|
+
Returns
|
|
482
|
+
-------
|
|
483
|
+
tuple
|
|
484
|
+
(commitment, randomness)
|
|
485
|
+
"""
|
|
486
|
+
return self._pedersen.commit(value, randomness)
|
|
487
|
+
|
|
488
|
+
def _prove_bit_or(self, bit: int, randomness: ZRElement, commitment: GElement) -> Dict[str, Any]:
|
|
489
|
+
"""
|
|
490
|
+
Create OR-proof that commitment contains 0 or 1.
|
|
491
|
+
|
|
492
|
+
Uses Schnorr OR-proof (Cramer-Damgard-Schoenmakers technique):
|
|
493
|
+
- Prover knows witness for one branch (the actual bit value)
|
|
494
|
+
- Simulates proof for the other branch
|
|
495
|
+
- Verifier cannot distinguish which branch is real
|
|
496
|
+
|
|
497
|
+
Parameters
|
|
498
|
+
----------
|
|
499
|
+
bit : int
|
|
500
|
+
The bit value (0 or 1)
|
|
501
|
+
randomness : ZR element
|
|
502
|
+
Randomness used in commitment C = g^bit * h^randomness
|
|
503
|
+
commitment : G element
|
|
504
|
+
The Pedersen commitment to verify
|
|
505
|
+
|
|
506
|
+
Returns
|
|
507
|
+
-------
|
|
508
|
+
dict
|
|
509
|
+
OR-proof containing commitments, challenges, and responses
|
|
510
|
+
"""
|
|
511
|
+
g = self._g
|
|
512
|
+
h = self._h
|
|
513
|
+
order = self.order
|
|
514
|
+
|
|
515
|
+
# For C = g^b * h^r, we prove b ∈ {0, 1}
|
|
516
|
+
# If b=0: C = h^r, prove knowledge of r s.t. C = h^r
|
|
517
|
+
# If b=1: C = g * h^r, prove knowledge of r s.t. C/g = h^r
|
|
518
|
+
|
|
519
|
+
# Random values for the real branch
|
|
520
|
+
k = self.group.random(ZR) # Real branch randomness
|
|
521
|
+
|
|
522
|
+
if bit == 0:
|
|
523
|
+
# Real branch: prove C = h^r (b=0)
|
|
524
|
+
# Simulated branch: C/g = h^r' (b=1)
|
|
525
|
+
|
|
526
|
+
# Commit for real branch (b=0)
|
|
527
|
+
A0 = h ** k # Real commitment
|
|
528
|
+
|
|
529
|
+
# Simulate b=1 branch: need (A1, e1, z1) s.t. h^z1 = A1 * (C/g)^e1
|
|
530
|
+
e1 = self.group.random(ZR)
|
|
531
|
+
z1 = self.group.random(ZR)
|
|
532
|
+
C_over_g = commitment * (g ** (-1))
|
|
533
|
+
A1 = (h ** z1) * (C_over_g ** (-int(e1) % order))
|
|
534
|
+
|
|
535
|
+
# Compute challenge e = H(g, h, C, A0, A1)
|
|
536
|
+
challenge_input = (b"OR_PROOF:", g, h, commitment, A0, A1)
|
|
537
|
+
e = self.group.hash(challenge_input, target_type=ZR)
|
|
538
|
+
e_int = int(e) % order
|
|
539
|
+
|
|
540
|
+
# Compute e0 = e - e1 (real challenge)
|
|
541
|
+
e1_int = int(e1) % order
|
|
542
|
+
e0_int = (e_int - e1_int) % order
|
|
543
|
+
e0 = self.group.init(ZR, e0_int)
|
|
544
|
+
|
|
545
|
+
# Compute z0 = k + e0 * r (real response)
|
|
546
|
+
r_int = int(randomness) % order
|
|
547
|
+
k_int = int(k) % order
|
|
548
|
+
z0_int = (k_int + e0_int * r_int) % order
|
|
549
|
+
z0 = self.group.init(ZR, z0_int)
|
|
550
|
+
|
|
551
|
+
else: # bit == 1
|
|
552
|
+
# Real branch: prove C/g = h^r (b=1)
|
|
553
|
+
# Simulated branch: C = h^r' (b=0)
|
|
554
|
+
|
|
555
|
+
# Simulate b=0 branch: need (A0, e0, z0) s.t. h^z0 = A0 * C^e0
|
|
556
|
+
e0 = self.group.random(ZR)
|
|
557
|
+
z0 = self.group.random(ZR)
|
|
558
|
+
A0 = (h ** z0) * (commitment ** (-int(e0) % order))
|
|
559
|
+
|
|
560
|
+
# Commit for real branch (b=1)
|
|
561
|
+
A1 = h ** k # Real commitment
|
|
562
|
+
|
|
563
|
+
# Compute challenge e = H(g, h, C, A0, A1)
|
|
564
|
+
challenge_input = (b"OR_PROOF:", g, h, commitment, A0, A1)
|
|
565
|
+
e = self.group.hash(challenge_input, target_type=ZR)
|
|
566
|
+
e_int = int(e) % order
|
|
567
|
+
|
|
568
|
+
# Compute e1 = e - e0 (real challenge)
|
|
569
|
+
e0_int = int(e0) % order
|
|
570
|
+
e1_int = (e_int - e0_int) % order
|
|
571
|
+
e1 = self.group.init(ZR, e1_int)
|
|
572
|
+
|
|
573
|
+
# Compute z1 = k + e1 * r (real response)
|
|
574
|
+
r_int = int(randomness) % order
|
|
575
|
+
k_int = int(k) % order
|
|
576
|
+
z1_int = (k_int + e1_int * r_int) % order
|
|
577
|
+
z1 = self.group.init(ZR, z1_int)
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
'A0': A0,
|
|
581
|
+
'A1': A1,
|
|
582
|
+
'e0': e0,
|
|
583
|
+
'e1': e1,
|
|
584
|
+
'z0': z0,
|
|
585
|
+
'z1': z1,
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
def _verify_bit_or(self, commitment: GElement, or_proof: Dict[str, Any]) -> bool:
|
|
589
|
+
"""
|
|
590
|
+
Verify OR-proof that commitment contains 0 or 1.
|
|
591
|
+
|
|
592
|
+
Parameters
|
|
593
|
+
----------
|
|
594
|
+
commitment : G element
|
|
595
|
+
Pedersen commitment to verify
|
|
596
|
+
or_proof : dict
|
|
597
|
+
OR-proof from _prove_bit_or
|
|
598
|
+
|
|
599
|
+
Returns
|
|
600
|
+
-------
|
|
601
|
+
bool
|
|
602
|
+
True if proof is valid, False otherwise
|
|
603
|
+
"""
|
|
604
|
+
g = self._g
|
|
605
|
+
h = self._h
|
|
606
|
+
order = self.order
|
|
607
|
+
|
|
608
|
+
A0 = or_proof['A0']
|
|
609
|
+
A1 = or_proof['A1']
|
|
610
|
+
e0 = or_proof['e0']
|
|
611
|
+
e1 = or_proof['e1']
|
|
612
|
+
z0 = or_proof['z0']
|
|
613
|
+
z1 = or_proof['z1']
|
|
614
|
+
|
|
615
|
+
# Verify challenge: e = e0 + e1 = H(g, h, C, A0, A1)
|
|
616
|
+
challenge_input = (b"OR_PROOF:", g, h, commitment, A0, A1)
|
|
617
|
+
e = self.group.hash(challenge_input, target_type=ZR)
|
|
618
|
+
e_int = int(e) % order
|
|
619
|
+
e0_int = int(e0) % order
|
|
620
|
+
e1_int = int(e1) % order
|
|
621
|
+
|
|
622
|
+
if (e0_int + e1_int) % order != e_int:
|
|
623
|
+
return False
|
|
624
|
+
|
|
625
|
+
# Verify b=0 branch: h^z0 = A0 * C^e0
|
|
626
|
+
lhs0 = h ** z0
|
|
627
|
+
rhs0 = A0 * (commitment ** e0)
|
|
628
|
+
if lhs0 != rhs0:
|
|
629
|
+
return False
|
|
630
|
+
|
|
631
|
+
# Verify b=1 branch: h^z1 = A1 * (C/g)^e1
|
|
632
|
+
C_over_g = commitment * (g ** (-1))
|
|
633
|
+
lhs1 = h ** z1
|
|
634
|
+
rhs1 = A1 * (C_over_g ** e1)
|
|
635
|
+
if lhs1 != rhs1:
|
|
636
|
+
return False
|
|
637
|
+
|
|
638
|
+
return True
|
|
639
|
+
|
|
640
|
+
def _prove_bit_decomposition(self, value_int: int, bits: List[int], value_randomness: ZRElement) -> Dict[str, Any]:
|
|
641
|
+
"""
|
|
642
|
+
Create ZK proof that bits are valid (0 or 1) and sum to value.
|
|
643
|
+
|
|
644
|
+
Parameters
|
|
645
|
+
----------
|
|
646
|
+
value_int : int
|
|
647
|
+
The value being decomposed
|
|
648
|
+
bits : list of int
|
|
649
|
+
The bit decomposition (each 0 or 1)
|
|
650
|
+
value_randomness : ZR element
|
|
651
|
+
Randomness used in the value commitment
|
|
652
|
+
|
|
653
|
+
Returns
|
|
654
|
+
-------
|
|
655
|
+
dict
|
|
656
|
+
ZK proof containing:
|
|
657
|
+
- bit_commitments: list of Pedersen commitments to each bit
|
|
658
|
+
- or_proofs: list of OR-proofs that each bit is 0 or 1
|
|
659
|
+
- sum_randomness: combined randomness for sum verification
|
|
660
|
+
"""
|
|
661
|
+
bit_commitments = []
|
|
662
|
+
bit_randomness = []
|
|
663
|
+
or_proofs = []
|
|
664
|
+
|
|
665
|
+
# Commit to each bit with fresh randomness
|
|
666
|
+
for i, bit in enumerate(bits):
|
|
667
|
+
r_i = self.group.random(ZR)
|
|
668
|
+
bit_randomness.append(r_i)
|
|
669
|
+
C_i, _ = self._pedersen_commit(bit, r_i)
|
|
670
|
+
bit_commitments.append(C_i)
|
|
671
|
+
|
|
672
|
+
# Generate OR-proof: C_i commits to 0 OR C_i commits to 1
|
|
673
|
+
or_proof = self._prove_bit_or(bit, r_i, C_i)
|
|
674
|
+
or_proofs.append(or_proof)
|
|
675
|
+
|
|
676
|
+
# Sum of bit randomness weighted by powers of 2 should equal value_randomness
|
|
677
|
+
# C = g^value * h^r = ∏ (g^{bit_i * 2^i} * h^{r_i * 2^i})
|
|
678
|
+
# = g^{∑ bit_i * 2^i} * h^{∑ r_i * 2^i}
|
|
679
|
+
# So: r = ∑ r_i * 2^i
|
|
680
|
+
# We provide the sum_randomness_diff = value_randomness - ∑ r_i * 2^i
|
|
681
|
+
# which should be 0 if honest, verifier can check
|
|
682
|
+
|
|
683
|
+
sum_r = 0
|
|
684
|
+
for i, r_i in enumerate(bit_randomness):
|
|
685
|
+
r_i_int = int(r_i) % self.order
|
|
686
|
+
sum_r = (sum_r + r_i_int * (1 << i)) % self.order
|
|
687
|
+
|
|
688
|
+
value_r_int = int(value_randomness) % self.order
|
|
689
|
+
# Diff should be 0 for honest prover
|
|
690
|
+
randomness_diff = (value_r_int - sum_r) % self.order
|
|
691
|
+
|
|
692
|
+
return {
|
|
693
|
+
'bit_commitments': bit_commitments,
|
|
694
|
+
'or_proofs': or_proofs,
|
|
695
|
+
'randomness_diff': randomness_diff,
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
def _verify_bit_decomposition(self, commitment: GElement, proof: Dict[str, Any]) -> bool:
|
|
699
|
+
"""
|
|
700
|
+
Verify ZK proof of correct bit decomposition.
|
|
701
|
+
|
|
702
|
+
Parameters
|
|
703
|
+
----------
|
|
704
|
+
commitment : G element
|
|
705
|
+
Pedersen commitment to the original value
|
|
706
|
+
proof : dict
|
|
707
|
+
Proof from _prove_bit_decomposition
|
|
708
|
+
|
|
709
|
+
Returns
|
|
710
|
+
-------
|
|
711
|
+
bool
|
|
712
|
+
True if proof is valid, False otherwise
|
|
713
|
+
"""
|
|
714
|
+
bit_commitments = proof['bit_commitments']
|
|
715
|
+
or_proofs = proof['or_proofs']
|
|
716
|
+
randomness_diff = proof['randomness_diff']
|
|
717
|
+
|
|
718
|
+
if len(bit_commitments) != len(or_proofs):
|
|
719
|
+
return False
|
|
720
|
+
|
|
721
|
+
# 1. Verify each OR-proof (bit is 0 or 1)
|
|
722
|
+
for i, (C_i, or_proof) in enumerate(zip(bit_commitments, or_proofs)):
|
|
723
|
+
if not self._verify_bit_or(C_i, or_proof):
|
|
724
|
+
logger.debug("OR-proof verification failed for bit %d", i)
|
|
725
|
+
return False
|
|
726
|
+
|
|
727
|
+
# 2. Verify sum proof: ∏ C_i^{2^i} * h^{diff} = C
|
|
728
|
+
# If bits sum correctly and randomness is consistent, this should hold
|
|
729
|
+
product = self.group.init(G, 1) # Identity element
|
|
730
|
+
for i, C_i in enumerate(bit_commitments):
|
|
731
|
+
power = 1 << i
|
|
732
|
+
product = product * (C_i ** power)
|
|
733
|
+
|
|
734
|
+
# Account for randomness difference (should be 0 for honest prover)
|
|
735
|
+
if randomness_diff != 0:
|
|
736
|
+
product = product * (self._h ** randomness_diff)
|
|
737
|
+
|
|
738
|
+
if product != commitment:
|
|
739
|
+
logger.debug("Sum verification failed: product != commitment")
|
|
740
|
+
return False
|
|
741
|
+
|
|
742
|
+
return True
|
|
743
|
+
|
|
744
|
+
def sender_commit(self, a: ZRElement) -> Dict[str, Any]:
|
|
745
|
+
"""
|
|
746
|
+
Sender commits to share a with ZK bit decomposition proof.
|
|
747
|
+
|
|
748
|
+
Parameters
|
|
749
|
+
----------
|
|
750
|
+
a : ZR element
|
|
751
|
+
Sender's multiplicative share
|
|
752
|
+
|
|
753
|
+
Returns
|
|
754
|
+
-------
|
|
755
|
+
dict
|
|
756
|
+
Commitment and bit decomposition proof to send to receiver
|
|
757
|
+
"""
|
|
758
|
+
self._a = a
|
|
759
|
+
a_int = int(a) % self.order
|
|
760
|
+
commitment, randomness = self._pedersen_commit(a)
|
|
761
|
+
self._commitment_randomness = randomness
|
|
762
|
+
self._sender_commit = commitment
|
|
763
|
+
|
|
764
|
+
# Decompose into bits
|
|
765
|
+
bits = bit_decompose(a_int, self.order, self.bit_length)
|
|
766
|
+
|
|
767
|
+
# Generate ZK proof of correct bit decomposition
|
|
768
|
+
bit_proof = self._prove_bit_decomposition(a_int, bits, randomness)
|
|
769
|
+
|
|
770
|
+
return {
|
|
771
|
+
'commitment': commitment,
|
|
772
|
+
'g': self._g,
|
|
773
|
+
'h': self._h,
|
|
774
|
+
'bit_proof': bit_proof,
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
def receiver_commit(self, b: ZRElement) -> Dict[str, Any]:
|
|
778
|
+
"""
|
|
779
|
+
Receiver commits to share b.
|
|
780
|
+
|
|
781
|
+
Parameters
|
|
782
|
+
----------
|
|
783
|
+
b : ZR element
|
|
784
|
+
Receiver's multiplicative share
|
|
785
|
+
|
|
786
|
+
Returns
|
|
787
|
+
-------
|
|
788
|
+
dict
|
|
789
|
+
Commitment to send to sender
|
|
790
|
+
"""
|
|
791
|
+
self._b = b
|
|
792
|
+
commitment, randomness = self._pedersen_commit(b)
|
|
793
|
+
self._receiver_randomness = randomness
|
|
794
|
+
self._receiver_commit = commitment
|
|
795
|
+
|
|
796
|
+
return {
|
|
797
|
+
'commitment': commitment,
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
def sender_round1(self, a: ZRElement, receiver_commit: Dict[str, Any]) -> Dict[str, Any]:
|
|
801
|
+
"""
|
|
802
|
+
Sender generates first message with receiver's commitment.
|
|
803
|
+
|
|
804
|
+
Parameters
|
|
805
|
+
----------
|
|
806
|
+
a : ZR element
|
|
807
|
+
Sender's multiplicative share
|
|
808
|
+
receiver_commit : dict
|
|
809
|
+
Receiver's commitment from receiver_commit
|
|
810
|
+
|
|
811
|
+
Returns
|
|
812
|
+
-------
|
|
813
|
+
dict
|
|
814
|
+
Message to send to receiver
|
|
815
|
+
"""
|
|
816
|
+
self._a = a
|
|
817
|
+
self._receiver_commit = receiver_commit['commitment']
|
|
818
|
+
|
|
819
|
+
# Run base MtA
|
|
820
|
+
mta_msg = self.mta.sender_round1(a)
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
'mta_msg': mta_msg,
|
|
824
|
+
'sender_commit': self._sender_commit,
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
def receiver_round1(self, b: ZRElement, sender_commit: Dict[str, Any], sender_msg: Dict[str, Any]) -> Tuple[Dict[str, Any], None]:
|
|
828
|
+
"""
|
|
829
|
+
Receiver processes sender message with commitments.
|
|
830
|
+
|
|
831
|
+
Parameters
|
|
832
|
+
----------
|
|
833
|
+
b : ZR element
|
|
834
|
+
Receiver's multiplicative share
|
|
835
|
+
sender_commit : dict
|
|
836
|
+
Sender's commitment from sender_commit (includes bit_proof)
|
|
837
|
+
sender_msg : dict
|
|
838
|
+
Message from sender_round1
|
|
839
|
+
|
|
840
|
+
Returns
|
|
841
|
+
-------
|
|
842
|
+
tuple
|
|
843
|
+
(receiver_message, beta_placeholder)
|
|
844
|
+
"""
|
|
845
|
+
self._b = b
|
|
846
|
+
self._g = sender_commit['g']
|
|
847
|
+
self._h = sender_commit['h']
|
|
848
|
+
self._sender_commit = sender_commit['commitment']
|
|
849
|
+
# Store bit decomposition proof for verification in receiver_verify
|
|
850
|
+
self._sender_bit_proof = sender_commit.get('bit_proof')
|
|
851
|
+
|
|
852
|
+
# Run base MtA - now returns (receiver_msg, None) since beta is computed later
|
|
853
|
+
mta_msg = sender_msg['mta_msg']
|
|
854
|
+
receiver_msg, _ = self.mta.receiver_round1(b, mta_msg)
|
|
855
|
+
|
|
856
|
+
# Add proof of correct computation
|
|
857
|
+
# In full implementation, this would include ZK proofs
|
|
858
|
+
return {
|
|
859
|
+
'mta_msg': receiver_msg,
|
|
860
|
+
'receiver_commit': self._receiver_commit,
|
|
861
|
+
}, None
|
|
862
|
+
|
|
863
|
+
def sender_round2(self, receiver_msg: Dict[str, Any]) -> Tuple[ZRElement, Dict[str, Any]]:
|
|
864
|
+
"""
|
|
865
|
+
Sender completes MtA and generates proof.
|
|
866
|
+
|
|
867
|
+
Parameters
|
|
868
|
+
----------
|
|
869
|
+
receiver_msg : dict
|
|
870
|
+
Message from receiver_round1
|
|
871
|
+
|
|
872
|
+
Returns
|
|
873
|
+
-------
|
|
874
|
+
tuple
|
|
875
|
+
(alpha, proof) where:
|
|
876
|
+
- alpha: sender's additive share
|
|
877
|
+
- proof: ZK proof of correctness (does NOT reveal sender_bits)
|
|
878
|
+
"""
|
|
879
|
+
mta_msg = receiver_msg['mta_msg']
|
|
880
|
+
# New MtA returns (alpha, ot_data) from sender_round2
|
|
881
|
+
alpha, ot_data = self.mta.sender_round2(mta_msg)
|
|
882
|
+
|
|
883
|
+
# Generate commitment-based proof that doesn't reveal the actual bits
|
|
884
|
+
# This proof verifies:
|
|
885
|
+
# 1. The commitment opens correctly
|
|
886
|
+
# 2. The bit decomposition is consistent with the committed value
|
|
887
|
+
# Using a Fiat-Shamir style challenge-response
|
|
888
|
+
a_int = int(self._a) % self.order
|
|
889
|
+
|
|
890
|
+
# Create challenge by hashing public values with domain separation
|
|
891
|
+
challenge_zr = self.group.hash(
|
|
892
|
+
(b"MTA_CHALLENGE:", self._sender_commit, self._g, self._h),
|
|
893
|
+
target_type=ZR
|
|
894
|
+
)
|
|
895
|
+
challenge = self.group.serialize(challenge_zr)
|
|
896
|
+
|
|
897
|
+
# Compute response: s = r + e*a (mod order)
|
|
898
|
+
# where r is the commitment randomness and e is the challenge
|
|
899
|
+
e = int(challenge_zr) % self.order
|
|
900
|
+
r_int = int(self._commitment_randomness) % self.order
|
|
901
|
+
s = (r_int + e * a_int) % self.order
|
|
902
|
+
|
|
903
|
+
# The proof consists of:
|
|
904
|
+
# - The challenge (derived from public values)
|
|
905
|
+
# - The response s
|
|
906
|
+
# - The commitment randomness (for Pedersen opening verification)
|
|
907
|
+
# This does NOT reveal the actual bits of 'a'
|
|
908
|
+
proof = {
|
|
909
|
+
'challenge': challenge,
|
|
910
|
+
'response': s,
|
|
911
|
+
'commitment_randomness': self._commitment_randomness,
|
|
912
|
+
'ot_data': ot_data, # For receiver to complete OT and get beta
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
return alpha, proof
|
|
916
|
+
|
|
917
|
+
def receiver_verify(self, proof: Dict[str, Any]) -> Tuple[Optional[ZRElement], bool]:
|
|
918
|
+
"""
|
|
919
|
+
Receiver verifies proof including ZK bit decomposition and returns beta.
|
|
920
|
+
|
|
921
|
+
Implements full ZK verification per DKLS23 Section 3:
|
|
922
|
+
1. Verifies challenge-response for commitment
|
|
923
|
+
2. Verifies bit decomposition OR-proofs (each bit is 0 or 1)
|
|
924
|
+
3. Verifies bits sum to the committed value
|
|
925
|
+
|
|
926
|
+
Parameters
|
|
927
|
+
----------
|
|
928
|
+
proof : dict
|
|
929
|
+
Proof from sender_round2
|
|
930
|
+
|
|
931
|
+
Returns
|
|
932
|
+
-------
|
|
933
|
+
tuple
|
|
934
|
+
(beta, valid) where:
|
|
935
|
+
- beta: receiver's additive share
|
|
936
|
+
- valid: boolean indicating if proof is valid
|
|
937
|
+
"""
|
|
938
|
+
commitment_randomness = proof['commitment_randomness']
|
|
939
|
+
challenge = proof['challenge']
|
|
940
|
+
response = proof['response']
|
|
941
|
+
ot_data = proof['ot_data']
|
|
942
|
+
|
|
943
|
+
# First, complete the OT to get beta
|
|
944
|
+
beta = self.mta.receiver_round2(ot_data)
|
|
945
|
+
self._beta = beta
|
|
946
|
+
|
|
947
|
+
# Check commitment exists
|
|
948
|
+
if self._sender_commit is None:
|
|
949
|
+
logger.debug("Verification failed: no sender commitment")
|
|
950
|
+
return None, False
|
|
951
|
+
|
|
952
|
+
# Verify the challenge was computed correctly with domain separation
|
|
953
|
+
expected_challenge_zr = self.group.hash(
|
|
954
|
+
(b"MTA_CHALLENGE:", self._sender_commit, self._g, self._h),
|
|
955
|
+
target_type=ZR
|
|
956
|
+
)
|
|
957
|
+
expected_challenge = self.group.serialize(expected_challenge_zr)
|
|
958
|
+
|
|
959
|
+
if challenge != expected_challenge:
|
|
960
|
+
logger.debug("Verification failed: challenge mismatch")
|
|
961
|
+
return None, False
|
|
962
|
+
|
|
963
|
+
# Verify response is in valid range
|
|
964
|
+
if response < 0 or response >= self.order:
|
|
965
|
+
logger.debug("Verification failed: response out of range")
|
|
966
|
+
return None, False
|
|
967
|
+
|
|
968
|
+
# Verify bit decomposition proof (DKLS23 Section 3 ZK verification)
|
|
969
|
+
# This proves that:
|
|
970
|
+
# 1. Each bit is 0 or 1 (via OR-proofs)
|
|
971
|
+
# 2. The bits sum to the committed value
|
|
972
|
+
if self._sender_bit_proof is None:
|
|
973
|
+
logger.debug("Verification failed: no bit decomposition proof")
|
|
974
|
+
return None, False
|
|
975
|
+
|
|
976
|
+
if not self._verify_bit_decomposition(
|
|
977
|
+
self._sender_commit,
|
|
978
|
+
self._sender_bit_proof
|
|
979
|
+
):
|
|
980
|
+
logger.debug("Verification failed: bit decomposition proof invalid")
|
|
981
|
+
return None, False
|
|
982
|
+
|
|
983
|
+
logger.debug("MtAwc verification successful")
|
|
984
|
+
return self._beta, True
|
|
985
|
+
|