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,589 @@
|
|
|
1
|
+
'''
|
|
2
|
+
IKNP-style OT Extension for Elliptic Curve Groups
|
|
3
|
+
|
|
4
|
+
| From: "Extending Oblivious Transfers Efficiently"
|
|
5
|
+
| By: Yuval Ishai, Joe Kilian, Kobbi Nissim, Erez Petrank
|
|
6
|
+
| Published: CRYPTO 2003
|
|
7
|
+
| URL: https://www.iacr.org/archive/crypto2003/27290145/27290145.pdf
|
|
8
|
+
|
|
9
|
+
* type: oblivious transfer extension
|
|
10
|
+
* setting: Symmetric primitives (hash, PRG, XOR)
|
|
11
|
+
* assumption: Random Oracle (SHA-256)
|
|
12
|
+
|
|
13
|
+
This module implements OT Extension which allows performing many OTs
|
|
14
|
+
with only a small number of base OT calls (k base OTs for m >> k OTs).
|
|
15
|
+
|
|
16
|
+
:Authors: Elton de Souza
|
|
17
|
+
:Date: 01/2026
|
|
18
|
+
'''
|
|
19
|
+
|
|
20
|
+
from charm.toolbox.securerandom import OpenSSLRand
|
|
21
|
+
from charm.toolbox.bitstring import Bytes
|
|
22
|
+
from charm.toolbox.ot.base_ot import SimpleOT
|
|
23
|
+
from charm.toolbox.symcrypto import AuthenticatedCryptoAbstraction
|
|
24
|
+
import hashlib
|
|
25
|
+
import logging
|
|
26
|
+
|
|
27
|
+
# Module logger
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def xor_bytes(a, b):
|
|
32
|
+
"""
|
|
33
|
+
XOR two byte strings of equal length.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
a : bytes
|
|
38
|
+
First byte string
|
|
39
|
+
b : bytes
|
|
40
|
+
Second byte string
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
bytes
|
|
45
|
+
XOR of the two byte strings
|
|
46
|
+
"""
|
|
47
|
+
assert len(a) == len(b), f"xor_bytes: operands differ in length ({len(a)} vs {len(b)})"
|
|
48
|
+
return bytes(x ^ y for x, y in zip(a, b))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def prg(seed, output_length):
|
|
52
|
+
"""
|
|
53
|
+
Pseudo-random generator using SHA-256 in counter mode.
|
|
54
|
+
|
|
55
|
+
Expands a seed to output_length bytes using hash chaining.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
seed : bytes
|
|
60
|
+
Random seed bytes
|
|
61
|
+
output_length : int
|
|
62
|
+
Desired output length in bytes
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
bytes
|
|
67
|
+
Pseudo-random bytes of specified length
|
|
68
|
+
"""
|
|
69
|
+
output = b''
|
|
70
|
+
counter = 0
|
|
71
|
+
while len(output) < output_length:
|
|
72
|
+
h = hashlib.sha256()
|
|
73
|
+
h.update(b"PRG:") # Domain separator
|
|
74
|
+
h.update(seed)
|
|
75
|
+
h.update(counter.to_bytes(4, 'big'))
|
|
76
|
+
output += h.digest()
|
|
77
|
+
counter += 1
|
|
78
|
+
return output[:output_length]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def hash_to_key(index, value):
|
|
82
|
+
"""
|
|
83
|
+
Hash index and value to derive a key for encryption.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
index : int
|
|
88
|
+
OT index
|
|
89
|
+
value : bytes
|
|
90
|
+
Value to hash
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
bytes
|
|
95
|
+
32-byte key
|
|
96
|
+
"""
|
|
97
|
+
h = hashlib.sha256()
|
|
98
|
+
h.update(b"KEY:") # Domain separator
|
|
99
|
+
h.update(index.to_bytes(8, 'big'))
|
|
100
|
+
h.update(value)
|
|
101
|
+
return h.digest()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def transpose_bit_matrix(matrix, rows, cols):
|
|
105
|
+
"""
|
|
106
|
+
Transpose a bit matrix represented as a list of byte rows.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
matrix : list of bytes
|
|
111
|
+
Matrix with 'rows' rows, each row being 'cols' bits (cols//8 bytes)
|
|
112
|
+
rows : int
|
|
113
|
+
Number of rows in input matrix
|
|
114
|
+
cols : int
|
|
115
|
+
Number of columns (bits) in input matrix
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
list of bytes
|
|
120
|
+
Transposed matrix with 'cols' rows, each row being 'rows' bits
|
|
121
|
+
"""
|
|
122
|
+
# Each row has cols bits = cols//8 bytes
|
|
123
|
+
# Result: cols rows, each with rows bits = rows//8 bytes
|
|
124
|
+
cols_bytes = (cols + 7) // 8
|
|
125
|
+
rows_bytes = (rows + 7) // 8
|
|
126
|
+
|
|
127
|
+
# Initialize result matrix
|
|
128
|
+
result = [bytearray(rows_bytes) for _ in range(cols)]
|
|
129
|
+
|
|
130
|
+
for i in range(rows):
|
|
131
|
+
row_bytes = matrix[i]
|
|
132
|
+
for j in range(cols):
|
|
133
|
+
# Get bit j from row i
|
|
134
|
+
byte_idx = j // 8
|
|
135
|
+
bit_idx = 7 - (j % 8)
|
|
136
|
+
if byte_idx < len(row_bytes):
|
|
137
|
+
bit = (row_bytes[byte_idx] >> bit_idx) & 1
|
|
138
|
+
else:
|
|
139
|
+
bit = 0
|
|
140
|
+
|
|
141
|
+
# Set bit i in column j (which becomes row j in result)
|
|
142
|
+
if bit:
|
|
143
|
+
result_byte_idx = i // 8
|
|
144
|
+
result_bit_idx = 7 - (i % 8)
|
|
145
|
+
result[j][result_byte_idx] |= (1 << result_bit_idx)
|
|
146
|
+
|
|
147
|
+
return [bytes(row) for row in result]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_bit(data, bit_index):
|
|
151
|
+
"""Get a specific bit from byte array."""
|
|
152
|
+
byte_idx = bit_index // 8
|
|
153
|
+
bit_idx = 7 - (bit_index % 8)
|
|
154
|
+
if byte_idx >= len(data):
|
|
155
|
+
return 0
|
|
156
|
+
return (data[byte_idx] >> bit_idx) & 1
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def set_bit(data, bit_index, value):
|
|
160
|
+
"""Set a specific bit in a bytearray."""
|
|
161
|
+
byte_idx = bit_index // 8
|
|
162
|
+
bit_idx = 7 - (bit_index % 8)
|
|
163
|
+
if value:
|
|
164
|
+
data[byte_idx] |= (1 << bit_idx)
|
|
165
|
+
else:
|
|
166
|
+
data[byte_idx] &= ~(1 << bit_idx)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class OTExtension:
|
|
170
|
+
"""
|
|
171
|
+
IKNP-style OT Extension.
|
|
172
|
+
|
|
173
|
+
Extends k base OTs to m OTs efficiently, where m >> k.
|
|
174
|
+
Uses the matrix transposition trick from the IKNP paper.
|
|
175
|
+
|
|
176
|
+
In the base OT phase, the roles are reversed:
|
|
177
|
+
- The OT Extension receiver acts as sender in base OT
|
|
178
|
+
- The OT Extension sender acts as receiver in base OT
|
|
179
|
+
|
|
180
|
+
The protocol flow is:
|
|
181
|
+
|
|
182
|
+
1. Base OT Setup (receiver acts as sender, sender acts as receiver):
|
|
183
|
+
- receiver_ext.receiver_setup_base_ots() -> base_ot_setups
|
|
184
|
+
- sender_ext.sender_setup_base_ots()
|
|
185
|
+
- sender_ext.sender_respond_base_ots(base_ot_setups) -> responses
|
|
186
|
+
- receiver_ext.receiver_transfer_seeds(responses) -> ciphertexts
|
|
187
|
+
- sender_ext.sender_receive_seeds(ciphertexts)
|
|
188
|
+
|
|
189
|
+
2. Extension Phase:
|
|
190
|
+
- sender_ext.sender_init()
|
|
191
|
+
- receiver_ext.receiver_extend(num_ots, choice_bits) -> msg, state
|
|
192
|
+
- sender_ext.sender_extend(num_ots, messages, msg) -> ciphertexts
|
|
193
|
+
- receiver_ext.receiver_output(ciphertexts, state) -> results
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def __init__(self, groupObj, security_param=128, base_ot=None):
|
|
197
|
+
"""
|
|
198
|
+
Initialize OT Extension with an elliptic curve group.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
groupObj : ECGroup
|
|
203
|
+
An elliptic curve group object from charm.toolbox.ecgroup
|
|
204
|
+
security_param : int
|
|
205
|
+
Security parameter (number of base OTs), typically 128
|
|
206
|
+
base_ot : SimpleOT or compatible, optional
|
|
207
|
+
Base OT protocol instance. If None, a SimpleOT instance is created.
|
|
208
|
+
"""
|
|
209
|
+
self.group = groupObj
|
|
210
|
+
self.k = security_param # number of base OTs / security parameter
|
|
211
|
+
self.rand = OpenSSLRand()
|
|
212
|
+
self._sender_random_bits = None # s in the protocol
|
|
213
|
+
self._sender_seeds = None # Seeds received from base OT (one per column)
|
|
214
|
+
self._receiver_seed_pairs = None # Seed pairs for receiver (both per column)
|
|
215
|
+
self._base_ot_complete = False
|
|
216
|
+
self._base_ot_states = None # Sender's base OT receiver states
|
|
217
|
+
self._base_ot_instances = None # Receiver's base OT sender instances
|
|
218
|
+
self._sender_ot_instances = None # Sender's base OT receiver instances
|
|
219
|
+
|
|
220
|
+
# Initialize base OT protocol
|
|
221
|
+
if base_ot is None:
|
|
222
|
+
self.base_ot = SimpleOT(groupObj)
|
|
223
|
+
else:
|
|
224
|
+
self.base_ot = base_ot
|
|
225
|
+
|
|
226
|
+
def sender_setup_base_ots(self):
|
|
227
|
+
"""
|
|
228
|
+
Sender-side base OT setup (acts as receiver in base OT).
|
|
229
|
+
|
|
230
|
+
Generates random k-bit string s and prepares for k base OTs
|
|
231
|
+
where sender will receive seed_j^{s_j} for each j.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
dict
|
|
236
|
+
Confirmation that sender is ready for base OT
|
|
237
|
+
"""
|
|
238
|
+
# Generate k random bits for s
|
|
239
|
+
k_bytes = (self.k + 7) // 8
|
|
240
|
+
self._sender_random_bits = self.rand.getRandomBytes(k_bytes)
|
|
241
|
+
|
|
242
|
+
# Prepare to receive k base OTs
|
|
243
|
+
self._sender_seeds = [None] * self.k
|
|
244
|
+
self._base_ot_states = [None] * self.k
|
|
245
|
+
|
|
246
|
+
return {'ready': True, 'k': self.k}
|
|
247
|
+
|
|
248
|
+
def receiver_setup_base_ots(self):
|
|
249
|
+
"""
|
|
250
|
+
Receiver-side base OT setup (acts as sender in base OT).
|
|
251
|
+
|
|
252
|
+
Generates k pairs of random seeds that will be transferred via base OT.
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
list of dict
|
|
257
|
+
List of k base OT setup messages to send to OT-ext sender
|
|
258
|
+
"""
|
|
259
|
+
# Generate k pairs of random seeds
|
|
260
|
+
self._receiver_seed_pairs = []
|
|
261
|
+
base_ot_setups = []
|
|
262
|
+
|
|
263
|
+
for j in range(self.k):
|
|
264
|
+
seed0 = self.rand.getRandomBytes(32)
|
|
265
|
+
seed1 = self.rand.getRandomBytes(32)
|
|
266
|
+
self._receiver_seed_pairs.append((seed0, seed1))
|
|
267
|
+
|
|
268
|
+
# Create a fresh base OT instance for this transfer
|
|
269
|
+
ot_instance = SimpleOT(self.group)
|
|
270
|
+
sender_params = ot_instance.sender_setup()
|
|
271
|
+
base_ot_setups.append({
|
|
272
|
+
'j': j,
|
|
273
|
+
'params': sender_params,
|
|
274
|
+
'ot_instance': ot_instance
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
self._base_ot_instances = [setup['ot_instance'] for setup in base_ot_setups]
|
|
278
|
+
return [{'j': s['j'], 'params': s['params']} for s in base_ot_setups]
|
|
279
|
+
|
|
280
|
+
def sender_respond_base_ots(self, base_ot_setups):
|
|
281
|
+
"""
|
|
282
|
+
Sender responds to receiver's base OT setup (acts as receiver, choosing based on s).
|
|
283
|
+
|
|
284
|
+
Parameters
|
|
285
|
+
----------
|
|
286
|
+
base_ot_setups : list of dict
|
|
287
|
+
Base OT parameters from receiver_setup_base_ots()
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
list of dict
|
|
292
|
+
Receiver responses for each base OT
|
|
293
|
+
"""
|
|
294
|
+
responses = []
|
|
295
|
+
self._sender_ot_instances = []
|
|
296
|
+
|
|
297
|
+
for setup in base_ot_setups:
|
|
298
|
+
j = setup['j']
|
|
299
|
+
params = setup['params']
|
|
300
|
+
s_j = get_bit(self._sender_random_bits, j)
|
|
301
|
+
|
|
302
|
+
# Act as receiver in base OT with choice bit s_j
|
|
303
|
+
ot_instance = SimpleOT(self.group)
|
|
304
|
+
response, state = ot_instance.receiver_choose(params, s_j)
|
|
305
|
+
|
|
306
|
+
self._sender_ot_instances.append(ot_instance)
|
|
307
|
+
self._base_ot_states[j] = state
|
|
308
|
+
responses.append({'j': j, 'response': response})
|
|
309
|
+
|
|
310
|
+
return responses
|
|
311
|
+
|
|
312
|
+
def receiver_transfer_seeds(self, sender_responses):
|
|
313
|
+
"""
|
|
314
|
+
Receiver completes base OT by transferring seeds.
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
----------
|
|
318
|
+
sender_responses : list of dict
|
|
319
|
+
Responses from sender_respond_base_ots()
|
|
320
|
+
|
|
321
|
+
Returns
|
|
322
|
+
-------
|
|
323
|
+
list of dict
|
|
324
|
+
Encrypted seed ciphertexts for each base OT
|
|
325
|
+
"""
|
|
326
|
+
ciphertexts = []
|
|
327
|
+
|
|
328
|
+
for resp in sender_responses:
|
|
329
|
+
j = resp['j']
|
|
330
|
+
response = resp['response']
|
|
331
|
+
seed0, seed1 = self._receiver_seed_pairs[j]
|
|
332
|
+
|
|
333
|
+
# Transfer both seeds via base OT
|
|
334
|
+
# Convert Bytes objects to native bytes for symcrypto compatibility
|
|
335
|
+
ot_instance = self._base_ot_instances[j]
|
|
336
|
+
cts = ot_instance.sender_transfer(response, bytes(seed0), bytes(seed1))
|
|
337
|
+
ciphertexts.append({'j': j, 'ciphertexts': cts})
|
|
338
|
+
|
|
339
|
+
self._base_ot_complete = True
|
|
340
|
+
return ciphertexts
|
|
341
|
+
|
|
342
|
+
def sender_receive_seeds(self, seed_ciphertexts):
|
|
343
|
+
"""
|
|
344
|
+
Sender receives seeds from base OT.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
seed_ciphertexts : list of dict
|
|
349
|
+
Encrypted seeds from receiver_transfer_seeds()
|
|
350
|
+
"""
|
|
351
|
+
for ct in seed_ciphertexts:
|
|
352
|
+
j = ct['j']
|
|
353
|
+
ciphertexts = ct['ciphertexts']
|
|
354
|
+
state = self._base_ot_states[j]
|
|
355
|
+
|
|
356
|
+
# Retrieve the seed corresponding to s_j
|
|
357
|
+
ot_instance = self._sender_ot_instances[j]
|
|
358
|
+
seed = ot_instance.receiver_retrieve(ciphertexts, state)
|
|
359
|
+
self._sender_seeds[j] = seed
|
|
360
|
+
|
|
361
|
+
self._base_ot_complete = True
|
|
362
|
+
|
|
363
|
+
def sender_init(self):
|
|
364
|
+
"""
|
|
365
|
+
Initialize sender for extension phase.
|
|
366
|
+
|
|
367
|
+
Must be called AFTER base OT setup is complete.
|
|
368
|
+
|
|
369
|
+
Returns
|
|
370
|
+
-------
|
|
371
|
+
dict
|
|
372
|
+
Confirmation that sender is ready (no secrets exposed)
|
|
373
|
+
|
|
374
|
+
Raises
|
|
375
|
+
------
|
|
376
|
+
RuntimeError
|
|
377
|
+
If base OT setup has not been completed
|
|
378
|
+
"""
|
|
379
|
+
if not self._base_ot_complete:
|
|
380
|
+
raise RuntimeError("Base OT setup must be completed before sender_init")
|
|
381
|
+
if self._sender_seeds is None or None in self._sender_seeds:
|
|
382
|
+
raise RuntimeError("Sender seeds not properly received from base OT")
|
|
383
|
+
|
|
384
|
+
return {'ready': True, 'k': self.k}
|
|
385
|
+
|
|
386
|
+
def receiver_extend(self, num_ots, choice_bits):
|
|
387
|
+
"""
|
|
388
|
+
Receiver side of the extension protocol.
|
|
389
|
+
|
|
390
|
+
Uses seeds from base OT setup instead of receiving s directly.
|
|
391
|
+
|
|
392
|
+
Parameters
|
|
393
|
+
----------
|
|
394
|
+
num_ots : int
|
|
395
|
+
Number of OTs to extend to
|
|
396
|
+
choice_bits : bytes
|
|
397
|
+
The receiver's m choice bits (m/8 bytes)
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
tuple
|
|
402
|
+
(message_to_sender, receiver_state)
|
|
403
|
+
|
|
404
|
+
Raises
|
|
405
|
+
------
|
|
406
|
+
RuntimeError
|
|
407
|
+
If base OT setup has not been completed
|
|
408
|
+
"""
|
|
409
|
+
if not self._base_ot_complete:
|
|
410
|
+
raise RuntimeError("Base OT setup must be completed before receiver_extend")
|
|
411
|
+
|
|
412
|
+
m = num_ots
|
|
413
|
+
m_bytes = (m + 7) // 8
|
|
414
|
+
|
|
415
|
+
# Build T matrices from both seed pairs
|
|
416
|
+
T0 = [] # T^0: columns from seed_j^0
|
|
417
|
+
T1 = [] # T^1: columns from seed_j^1
|
|
418
|
+
|
|
419
|
+
for j in range(self.k):
|
|
420
|
+
seed0, seed1 = self._receiver_seed_pairs[j]
|
|
421
|
+
T0.append(prg(seed0, m_bytes))
|
|
422
|
+
T1.append(prg(seed1, m_bytes))
|
|
423
|
+
|
|
424
|
+
# Compute U_j = T_j^0 ⊕ T_j^1 ⊕ choice_bits
|
|
425
|
+
# This ensures: Q_j = T_j^{s_j} when sender computes Q_j = recv_j ⊕ (s_j · U_j)
|
|
426
|
+
U = []
|
|
427
|
+
for j in range(self.k):
|
|
428
|
+
u_j = xor_bytes(xor_bytes(T0[j], T1[j]), choice_bits)
|
|
429
|
+
U.append(u_j)
|
|
430
|
+
|
|
431
|
+
# For receiver's keys: t_i is ALWAYS row i of T^0 transposed
|
|
432
|
+
# This is because:
|
|
433
|
+
# - Sender computes Q_j = T_j^0 ⊕ (s_j · r), so q_i = t_i^0 ⊕ (r_i · s)
|
|
434
|
+
# - If r_i = 0: key0 = H(i, q_i) = H(i, t_i^0), key1 = H(i, t_i^0 ⊕ s)
|
|
435
|
+
# Receiver wants m0, can compute H(i, t_i^0) ✓
|
|
436
|
+
# - If r_i = 1: key0 = H(i, t_i^0 ⊕ s), key1 = H(i, t_i^0)
|
|
437
|
+
# Receiver wants m1, can compute H(i, t_i^0) ✓
|
|
438
|
+
T0_transposed = transpose_bit_matrix(T0, self.k, m)
|
|
439
|
+
t_rows = T0_transposed # Always use T^0
|
|
440
|
+
|
|
441
|
+
receiver_state = {
|
|
442
|
+
'num_ots': m,
|
|
443
|
+
'choice_bits': choice_bits,
|
|
444
|
+
't_rows': t_rows,
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
message_to_sender = {
|
|
448
|
+
'U': U, # Send U matrix instead of Q
|
|
449
|
+
'num_ots': m,
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
logger.debug("Receiver extend: m=%d, k=%d", m, self.k)
|
|
453
|
+
|
|
454
|
+
return message_to_sender, receiver_state
|
|
455
|
+
|
|
456
|
+
def sender_extend(self, num_ots, message_pairs, receiver_msg):
|
|
457
|
+
"""
|
|
458
|
+
Sender side of the extension protocol.
|
|
459
|
+
|
|
460
|
+
The sender:
|
|
461
|
+
1. Receives U matrix from receiver
|
|
462
|
+
2. Computes T from seeds: T_j = PRG(seed_j^{s_j})
|
|
463
|
+
3. Computes Q_j = T_j ⊕ (s_j · U_j)
|
|
464
|
+
4. For each i, computes q_i (row i of Q^T)
|
|
465
|
+
5. Encrypts x_{i,0} with H(i, q_i)
|
|
466
|
+
6. Encrypts x_{i,1} with H(i, q_i XOR s)
|
|
467
|
+
|
|
468
|
+
Parameters
|
|
469
|
+
----------
|
|
470
|
+
num_ots : int
|
|
471
|
+
Number of OTs
|
|
472
|
+
message_pairs : list of tuples
|
|
473
|
+
List of (m0, m1) byte message pairs
|
|
474
|
+
receiver_msg : dict
|
|
475
|
+
Message from receiver_extend containing U matrix
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
list of tuples
|
|
480
|
+
List of (y0, y1) encrypted message pairs
|
|
481
|
+
|
|
482
|
+
Raises
|
|
483
|
+
------
|
|
484
|
+
RuntimeError
|
|
485
|
+
If base OT or sender_init was not completed
|
|
486
|
+
"""
|
|
487
|
+
if not self._base_ot_complete:
|
|
488
|
+
raise RuntimeError("Base OT setup must be completed before sender_extend")
|
|
489
|
+
if self._sender_random_bits is None:
|
|
490
|
+
raise RuntimeError("sender_init must be called before sender_extend")
|
|
491
|
+
|
|
492
|
+
m = num_ots
|
|
493
|
+
m_bytes = (m + 7) // 8
|
|
494
|
+
U = receiver_msg['U']
|
|
495
|
+
s = self._sender_random_bits
|
|
496
|
+
|
|
497
|
+
# Build T from received seeds: T_j = PRG(seed_j^{s_j})
|
|
498
|
+
T = []
|
|
499
|
+
for j in range(self.k):
|
|
500
|
+
T.append(prg(self._sender_seeds[j], m_bytes))
|
|
501
|
+
|
|
502
|
+
# Compute Q_j = T_j ⊕ (s_j · U_j)
|
|
503
|
+
Q = []
|
|
504
|
+
for j in range(self.k):
|
|
505
|
+
s_j = get_bit(s, j)
|
|
506
|
+
if s_j == 0:
|
|
507
|
+
Q.append(T[j])
|
|
508
|
+
else:
|
|
509
|
+
Q.append(xor_bytes(T[j], U[j]))
|
|
510
|
+
|
|
511
|
+
# Transpose Q to get q_i for each i
|
|
512
|
+
Q_transposed = transpose_bit_matrix(Q, self.k, m)
|
|
513
|
+
|
|
514
|
+
ciphertexts = []
|
|
515
|
+
for i in range(m):
|
|
516
|
+
q_i = Q_transposed[i]
|
|
517
|
+
|
|
518
|
+
# Key for m0: H(i, q_i)
|
|
519
|
+
key0 = hash_to_key(i, q_i)
|
|
520
|
+
|
|
521
|
+
# Key for m1: H(i, q_i XOR s)
|
|
522
|
+
q_i_xor_s = xor_bytes(q_i, s[:len(q_i)])
|
|
523
|
+
key1 = hash_to_key(i, q_i_xor_s)
|
|
524
|
+
|
|
525
|
+
# Encrypt messages using authenticated encryption (AEAD)
|
|
526
|
+
m0, m1 = message_pairs[i]
|
|
527
|
+
|
|
528
|
+
# Use first 16 bytes of key for AES (AuthenticatedCryptoAbstraction requirement)
|
|
529
|
+
cipher0 = AuthenticatedCryptoAbstraction(key0)
|
|
530
|
+
cipher1 = AuthenticatedCryptoAbstraction(key1)
|
|
531
|
+
|
|
532
|
+
y0 = cipher0.encrypt(m0)
|
|
533
|
+
y1 = cipher1.encrypt(m1)
|
|
534
|
+
|
|
535
|
+
ciphertexts.append((y0, y1))
|
|
536
|
+
|
|
537
|
+
if Q_transposed:
|
|
538
|
+
q0_hex = Q_transposed[0][:8].hex() if len(Q_transposed[0]) >= 8 else Q_transposed[0].hex()
|
|
539
|
+
logger.debug("Sender extend: m=%d, Q_transposed[0][:8]=%s", m, q0_hex)
|
|
540
|
+
|
|
541
|
+
return ciphertexts
|
|
542
|
+
|
|
543
|
+
def receiver_output(self, ciphertexts, receiver_state):
|
|
544
|
+
"""
|
|
545
|
+
Receiver decrypts the chosen messages.
|
|
546
|
+
|
|
547
|
+
The receiver uses t_i (from receiver_extend) to decrypt:
|
|
548
|
+
- If r_i = 0: decrypt with H(i, t_i)
|
|
549
|
+
- If r_i = 1: decrypt with H(i, t_i) (which equals H(i, q_i XOR s) for correct choice)
|
|
550
|
+
|
|
551
|
+
Parameters
|
|
552
|
+
----------
|
|
553
|
+
ciphertexts : list of tuples
|
|
554
|
+
Encrypted message pairs from sender_extend
|
|
555
|
+
receiver_state : dict
|
|
556
|
+
State from receiver_extend
|
|
557
|
+
|
|
558
|
+
Returns
|
|
559
|
+
-------
|
|
560
|
+
list of bytes
|
|
561
|
+
The decrypted chosen messages
|
|
562
|
+
"""
|
|
563
|
+
m = receiver_state['num_ots']
|
|
564
|
+
choice_bits = receiver_state['choice_bits']
|
|
565
|
+
t_rows = receiver_state['t_rows']
|
|
566
|
+
|
|
567
|
+
results = []
|
|
568
|
+
for i in range(m):
|
|
569
|
+
t_i = t_rows[i]
|
|
570
|
+
r_i = get_bit(choice_bits, i)
|
|
571
|
+
|
|
572
|
+
# Key: H(i, t_i)
|
|
573
|
+
key = hash_to_key(i, t_i)
|
|
574
|
+
|
|
575
|
+
# Get the ciphertext corresponding to choice
|
|
576
|
+
y0, y1 = ciphertexts[i]
|
|
577
|
+
y = y1 if r_i else y0
|
|
578
|
+
|
|
579
|
+
# Decrypt with authentication
|
|
580
|
+
cipher = AuthenticatedCryptoAbstraction(key)
|
|
581
|
+
try:
|
|
582
|
+
msg = cipher.decrypt(y)
|
|
583
|
+
except ValueError as e:
|
|
584
|
+
raise ValueError(f"Authentication failed for OT index {i}: ciphertext may have been tampered") from e
|
|
585
|
+
results.append(msg)
|
|
586
|
+
|
|
587
|
+
logger.debug("Receiver output: m=%d", m)
|
|
588
|
+
|
|
589
|
+
return results
|