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/ot/dpf.py
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Distributed Point Function (DPF) based on GGM Construction
|
|
3
|
+
|
|
4
|
+
| From: "Function Secret Sharing: Improvements and Extensions"
|
|
5
|
+
| By: Elette Boyle, Niv Gilboa, Yuval Ishai
|
|
6
|
+
| Published: CCS 2016
|
|
7
|
+
| URL: https://eprint.iacr.org/2018/707
|
|
8
|
+
|
|
|
9
|
+
| Also based on GGM PRF construction from:
|
|
10
|
+
| "How to Construct Random Functions" - Goldreich, Goldwasser, Micali (JACM 1986)
|
|
11
|
+
| URL: https://people.csail.mit.edu/silvio/Selected%20Scientific%20Papers/Pseudo%20Randomness/How%20To%20Construct%20Random%20Functions.pdf
|
|
12
|
+
|
|
13
|
+
* type: function secret sharing
|
|
14
|
+
* setting: symmetric key
|
|
15
|
+
* assumption: PRG security
|
|
16
|
+
|
|
17
|
+
:Authors: Elton de Souza
|
|
18
|
+
:Date: 01/2026
|
|
19
|
+
'''
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
import logging
|
|
23
|
+
from typing import Tuple, List
|
|
24
|
+
|
|
25
|
+
# Module logger
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
# Key serialization version
|
|
29
|
+
KEY_VERSION = 1
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def xor_bytes(a: bytes, b: bytes) -> bytes:
|
|
33
|
+
"""
|
|
34
|
+
XOR two byte strings of equal length.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
a : bytes
|
|
39
|
+
First byte string
|
|
40
|
+
b : bytes
|
|
41
|
+
Second byte string
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
bytes
|
|
46
|
+
XOR of the two byte strings
|
|
47
|
+
|
|
48
|
+
>>> xor_bytes(b'\\x00\\xff', b'\\xff\\x00')
|
|
49
|
+
b'\\xff\\xff'
|
|
50
|
+
>>> xor_bytes(b'\\xab\\xcd', b'\\xab\\xcd')
|
|
51
|
+
b'\\x00\\x00'
|
|
52
|
+
"""
|
|
53
|
+
assert len(a) == len(b), f"xor_bytes: operands differ in length ({len(a)} vs {len(b)})"
|
|
54
|
+
return bytes(x ^ y for x, y in zip(a, b))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DPF:
|
|
58
|
+
"""
|
|
59
|
+
Distributed Point Function (DPF) based on GGM PRF construction.
|
|
60
|
+
|
|
61
|
+
A DPF allows secret-sharing a point function f_{α,β}(x) = β if x=α else 0
|
|
62
|
+
between two parties, such that:
|
|
63
|
+
- Neither party learns α or β individually
|
|
64
|
+
- The sum of both evaluations equals f_{α,β}(x)
|
|
65
|
+
|
|
66
|
+
The construction uses a GGM-style binary tree with a length-doubling PRG.
|
|
67
|
+
Key size is O(λ * log n) for domain size n.
|
|
68
|
+
|
|
69
|
+
>>> dpf = DPF(security_param=128, domain_bits=8)
|
|
70
|
+
>>> # Create point function f(5) = 42, f(x) = 0 for x != 5
|
|
71
|
+
>>> k0, k1 = dpf.gen(alpha=5, beta=42)
|
|
72
|
+
>>> # Evaluate at target point - sum should equal beta
|
|
73
|
+
>>> y0 = dpf.eval(0, k0, 5)
|
|
74
|
+
>>> y1 = dpf.eval(1, k1, 5)
|
|
75
|
+
>>> (y0 + y1) % (2**64)
|
|
76
|
+
42
|
|
77
|
+
>>> # Evaluate at non-target point - sum should equal 0
|
|
78
|
+
>>> z0 = dpf.eval(0, k0, 7)
|
|
79
|
+
>>> z1 = dpf.eval(1, k1, 7)
|
|
80
|
+
>>> (z0 + z1) % (2**64)
|
|
81
|
+
0
|
|
82
|
+
|
|
83
|
+
Security Limitations
|
|
84
|
+
--------------------
|
|
85
|
+
WARNING: This implementation is NOT constant-time and is vulnerable to
|
|
86
|
+
timing attacks. This implementation is suitable for research and educational
|
|
87
|
+
purposes only.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(self, security_param: int = 128, domain_bits: int = 20):
|
|
91
|
+
"""
|
|
92
|
+
Initialize DPF with security parameter and domain size.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
security_param : int
|
|
97
|
+
Security parameter in bits (default: 128)
|
|
98
|
+
domain_bits : int
|
|
99
|
+
Domain size is 2^domain_bits (default: 20, giving 1M points)
|
|
100
|
+
"""
|
|
101
|
+
if security_param not in (128, 256):
|
|
102
|
+
raise ValueError("security_param must be 128 or 256")
|
|
103
|
+
if domain_bits < 1 or domain_bits > 32:
|
|
104
|
+
raise ValueError("domain_bits must be between 1 and 32")
|
|
105
|
+
|
|
106
|
+
self.lambda_bytes = security_param // 8 # 16 bytes for 128-bit security
|
|
107
|
+
self.n = domain_bits # log2 of domain size
|
|
108
|
+
self.domain_size = 1 << domain_bits # 2^n
|
|
109
|
+
|
|
110
|
+
logger.debug("DPF initialized: lambda=%d bits, domain=2^%d", security_param, domain_bits)
|
|
111
|
+
|
|
112
|
+
def prg(self, seed: bytes) -> Tuple[bytes, bytes]:
|
|
113
|
+
"""
|
|
114
|
+
Length-doubling PRG using SHA-256.
|
|
115
|
+
|
|
116
|
+
Expands a λ-bit seed to (2λ + 2) bits by hashing with domain separators.
|
|
117
|
+
Returns two seeds (each λ bits) and extracts 2 control bits.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
seed : bytes
|
|
122
|
+
Input seed of lambda_bytes length
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
tuple
|
|
127
|
+
((left_seed, left_bit), (right_seed, right_bit)) where:
|
|
128
|
+
- left_seed, right_seed are bytes of lambda_bytes length
|
|
129
|
+
- left_bit, right_bit are control bits (0 or 1)
|
|
130
|
+
|
|
131
|
+
>>> dpf = DPF(security_param=128, domain_bits=8)
|
|
132
|
+
>>> seed = b'\\x00' * 16
|
|
133
|
+
>>> (left, left_bit), (right, right_bit) = dpf.prg(seed)
|
|
134
|
+
>>> len(left) == 16 and len(right) == 16
|
|
135
|
+
True
|
|
136
|
+
>>> left_bit in (0, 1) and right_bit in (0, 1)
|
|
137
|
+
True
|
|
138
|
+
"""
|
|
139
|
+
assert len(seed) == self.lambda_bytes, f"Seed must be {self.lambda_bytes} bytes"
|
|
140
|
+
|
|
141
|
+
# Hash with domain separator for left child
|
|
142
|
+
h_left = hashlib.sha256()
|
|
143
|
+
h_left.update(b'\x00') # domain separator for left
|
|
144
|
+
h_left.update(seed)
|
|
145
|
+
left_output = h_left.digest()
|
|
146
|
+
|
|
147
|
+
# Hash with domain separator for right child
|
|
148
|
+
h_right = hashlib.sha256()
|
|
149
|
+
h_right.update(b'\x01') # domain separator for right
|
|
150
|
+
h_right.update(seed)
|
|
151
|
+
right_output = h_right.digest()
|
|
152
|
+
|
|
153
|
+
# Extract seeds and control bits
|
|
154
|
+
left_seed = left_output[:self.lambda_bytes]
|
|
155
|
+
left_bit = left_output[self.lambda_bytes] & 1
|
|
156
|
+
|
|
157
|
+
right_seed = right_output[:self.lambda_bytes]
|
|
158
|
+
right_bit = right_output[self.lambda_bytes] & 1
|
|
159
|
+
|
|
160
|
+
return (left_seed, left_bit), (right_seed, right_bit)
|
|
161
|
+
|
|
162
|
+
def _get_bit(self, x: int, level: int) -> int:
|
|
163
|
+
"""
|
|
164
|
+
Get bit at position level from MSB of x.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
x : int
|
|
169
|
+
The value to extract bit from
|
|
170
|
+
level : int
|
|
171
|
+
The bit position (0 is MSB for n-bit value)
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
int
|
|
176
|
+
0 or 1
|
|
177
|
+
"""
|
|
178
|
+
return (x >> (self.n - 1 - level)) & 1
|
|
179
|
+
|
|
180
|
+
def _convert_output(self, seed: bytes) -> int:
|
|
181
|
+
"""
|
|
182
|
+
Convert a seed to an integer output value.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
seed : bytes
|
|
187
|
+
Seed bytes
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
int
|
|
192
|
+
64-bit integer value
|
|
193
|
+
"""
|
|
194
|
+
# Hash the seed and take first 8 bytes as output
|
|
195
|
+
h = hashlib.sha256()
|
|
196
|
+
h.update(b'\x02') # domain separator for output conversion
|
|
197
|
+
h.update(seed)
|
|
198
|
+
return int.from_bytes(h.digest()[:8], 'big')
|
|
199
|
+
|
|
200
|
+
def gen(self, alpha: int, beta: int) -> Tuple[bytes, bytes]:
|
|
201
|
+
"""
|
|
202
|
+
Generate DPF keys for point function f_{α,β}.
|
|
203
|
+
|
|
204
|
+
Creates keys (k0, k1) such that:
|
|
205
|
+
- eval(0, k0, x) + eval(1, k1, x) = β if x = α
|
|
206
|
+
- eval(0, k0, x) + eval(1, k1, x) = 0 if x ≠ α
|
|
207
|
+
|
|
208
|
+
The algorithm walks down the GGM tree from root to leaf α,
|
|
209
|
+
generating correction words at each level to "fix" the off-path
|
|
210
|
+
sibling values.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
alpha : int
|
|
215
|
+
The target point (must be in [0, 2^n))
|
|
216
|
+
beta : int
|
|
217
|
+
The output value at target point
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
tuple
|
|
222
|
+
(k0, k1) where each key is a bytes object containing:
|
|
223
|
+
- Initial seed (λ bytes)
|
|
224
|
+
- Initial control bit (1 byte)
|
|
225
|
+
- Correction words (n * (2λ + 2) bytes)
|
|
226
|
+
- Final correction word (8 bytes for 64-bit output)
|
|
227
|
+
|
|
228
|
+
Raises
|
|
229
|
+
------
|
|
230
|
+
ValueError
|
|
231
|
+
If alpha is out of range
|
|
232
|
+
|
|
233
|
+
>>> dpf = DPF(security_param=128, domain_bits=4)
|
|
234
|
+
>>> k0, k1 = dpf.gen(alpha=3, beta=100)
|
|
235
|
+
>>> len(k0) > 0 and len(k1) > 0
|
|
236
|
+
True
|
|
237
|
+
"""
|
|
238
|
+
if not (0 <= alpha < self.domain_size):
|
|
239
|
+
raise ValueError(f"alpha must be in [0, {self.domain_size})")
|
|
240
|
+
|
|
241
|
+
import os
|
|
242
|
+
|
|
243
|
+
# Sample random initial seeds for both parties
|
|
244
|
+
s0 = os.urandom(self.lambda_bytes)
|
|
245
|
+
s1 = os.urandom(self.lambda_bytes)
|
|
246
|
+
|
|
247
|
+
# Initial control bits: t0 = 0, t1 = 1 (parties start different)
|
|
248
|
+
t0 = 0
|
|
249
|
+
t1 = 1
|
|
250
|
+
|
|
251
|
+
# Store correction words
|
|
252
|
+
correction_words = []
|
|
253
|
+
|
|
254
|
+
# Current seeds and control bits along the path to alpha
|
|
255
|
+
s0_curr, s1_curr = s0, s1
|
|
256
|
+
t0_curr, t1_curr = t0, t1
|
|
257
|
+
|
|
258
|
+
logger.debug("DPF gen: alpha=%d, beta=%d, n=%d", alpha, beta, self.n)
|
|
259
|
+
|
|
260
|
+
# Walk down tree from root to leaf alpha
|
|
261
|
+
for level in range(self.n):
|
|
262
|
+
# Get direction at this level (0=left, 1=right)
|
|
263
|
+
alpha_bit = self._get_bit(alpha, level)
|
|
264
|
+
|
|
265
|
+
# Expand both current seeds
|
|
266
|
+
(s0_left, t0_left), (s0_right, t0_right) = self.prg(s0_curr)
|
|
267
|
+
(s1_left, t1_left), (s1_right, t1_right) = self.prg(s1_curr)
|
|
268
|
+
|
|
269
|
+
# The correction word should make the "lose" (off-path) children
|
|
270
|
+
# have equal seeds s0_lose' = s1_lose' and equal control bits t0_lose' = t1_lose'
|
|
271
|
+
# The "keep" (on-path) children should maintain t0_keep' XOR t1_keep' = 1
|
|
272
|
+
|
|
273
|
+
# s_cw = s0_lose XOR s1_lose (so after XOR both have same value)
|
|
274
|
+
if alpha_bit == 0:
|
|
275
|
+
# Keep left, lose right
|
|
276
|
+
s_cw_left = xor_bytes(s0_left, s1_left)
|
|
277
|
+
s_cw_right = xor_bytes(s0_right, s1_right)
|
|
278
|
+
else:
|
|
279
|
+
# Keep right, lose left
|
|
280
|
+
s_cw_left = xor_bytes(s0_left, s1_left)
|
|
281
|
+
s_cw_right = xor_bytes(s0_right, s1_right)
|
|
282
|
+
|
|
283
|
+
# For control bit correction:
|
|
284
|
+
# After applying CW (based on t_curr), we want:
|
|
285
|
+
# - On "lose" path: t0' XOR t1' = 0 (both same, so output cancels)
|
|
286
|
+
# - On "keep" path: t0' XOR t1' = 1 (different, maintains invariant)
|
|
287
|
+
#
|
|
288
|
+
# Let L = alpha_bit (1 if going left is "lose")
|
|
289
|
+
# t_new = t_old XOR (t_curr * t_cw)
|
|
290
|
+
#
|
|
291
|
+
# For lose side (L=1 means left is keep, 1-L means left is lose):
|
|
292
|
+
# t0_lose' XOR t1_lose' = (t0_lose XOR t0_curr*t_cw) XOR (t1_lose XOR t1_curr*t_cw)
|
|
293
|
+
# = t0_lose XOR t1_lose XOR (t0_curr XOR t1_curr)*t_cw
|
|
294
|
+
# Since t0_curr XOR t1_curr = 1, we need t_cw = t0_lose XOR t1_lose for them to equal.
|
|
295
|
+
#
|
|
296
|
+
# For keep side:
|
|
297
|
+
# Similarly, t_cw_keep = t0_keep XOR t1_keep XOR 1
|
|
298
|
+
|
|
299
|
+
# Correction for control bits
|
|
300
|
+
# For left (keep if alpha_bit=0, lose if alpha_bit=1):
|
|
301
|
+
if alpha_bit == 0:
|
|
302
|
+
# left is keep: want t0_left' XOR t1_left' = 1
|
|
303
|
+
t_cw_left = t0_left ^ t1_left ^ 1
|
|
304
|
+
# right is lose: want t0_right' XOR t1_right' = 0
|
|
305
|
+
t_cw_right = t0_right ^ t1_right
|
|
306
|
+
else:
|
|
307
|
+
# left is lose: want t0_left' XOR t1_left' = 0
|
|
308
|
+
t_cw_left = t0_left ^ t1_left
|
|
309
|
+
# right is keep: want t0_right' XOR t1_right' = 1
|
|
310
|
+
t_cw_right = t0_right ^ t1_right ^ 1
|
|
311
|
+
|
|
312
|
+
# Store correction word: (s_left, t_left, s_right, t_right)
|
|
313
|
+
cw = (s_cw_left, t_cw_left, s_cw_right, t_cw_right)
|
|
314
|
+
correction_words.append(cw)
|
|
315
|
+
|
|
316
|
+
# Apply correction based on control bit and compute new seeds
|
|
317
|
+
# Party 0's new values
|
|
318
|
+
s0_left_new = s0_left
|
|
319
|
+
t0_left_new = t0_left
|
|
320
|
+
s0_right_new = s0_right
|
|
321
|
+
t0_right_new = t0_right
|
|
322
|
+
if t0_curr == 1:
|
|
323
|
+
s0_left_new = xor_bytes(s0_left, s_cw_left)
|
|
324
|
+
t0_left_new = t0_left ^ t_cw_left
|
|
325
|
+
s0_right_new = xor_bytes(s0_right, s_cw_right)
|
|
326
|
+
t0_right_new = t0_right ^ t_cw_right
|
|
327
|
+
|
|
328
|
+
# Party 1's new values
|
|
329
|
+
s1_left_new = s1_left
|
|
330
|
+
t1_left_new = t1_left
|
|
331
|
+
s1_right_new = s1_right
|
|
332
|
+
t1_right_new = t1_right
|
|
333
|
+
if t1_curr == 1:
|
|
334
|
+
s1_left_new = xor_bytes(s1_left, s_cw_left)
|
|
335
|
+
t1_left_new = t1_left ^ t_cw_left
|
|
336
|
+
s1_right_new = xor_bytes(s1_right, s_cw_right)
|
|
337
|
+
t1_right_new = t1_right ^ t_cw_right
|
|
338
|
+
|
|
339
|
+
# Move to next level along path to alpha
|
|
340
|
+
if alpha_bit == 0:
|
|
341
|
+
s0_curr, t0_curr = s0_left_new, t0_left_new
|
|
342
|
+
s1_curr, t1_curr = s1_left_new, t1_left_new
|
|
343
|
+
else:
|
|
344
|
+
s0_curr, t0_curr = s0_right_new, t0_right_new
|
|
345
|
+
s1_curr, t1_curr = s1_right_new, t1_right_new
|
|
346
|
+
|
|
347
|
+
# Final correction word to encode beta
|
|
348
|
+
# At leaf alpha: we have t0_curr XOR t1_curr = 1
|
|
349
|
+
#
|
|
350
|
+
# Eval computes:
|
|
351
|
+
# - Party 0's output = convert(s0) + t0 * final_cw
|
|
352
|
+
# - Party 1's output = -convert(s1) - t1 * final_cw
|
|
353
|
+
#
|
|
354
|
+
# For off-path (s0=s1, t0=t1):
|
|
355
|
+
# Sum = convert(s) - convert(s) + t*final_cw - t*final_cw = 0 ✓
|
|
356
|
+
#
|
|
357
|
+
# For on-path (t0 XOR t1 = 1):
|
|
358
|
+
# If t0=0, t1=1: Sum = convert(s0) - convert(s1) - final_cw
|
|
359
|
+
# Want: convert(s0) - convert(s1) - final_cw = beta
|
|
360
|
+
# So: final_cw = out0 - out1 - beta
|
|
361
|
+
#
|
|
362
|
+
# If t0=1, t1=0: Sum = convert(s0) + final_cw - convert(s1)
|
|
363
|
+
# Want: convert(s0) - convert(s1) + final_cw = beta
|
|
364
|
+
# So: final_cw = beta - out0 + out1
|
|
365
|
+
|
|
366
|
+
out0 = self._convert_output(s0_curr)
|
|
367
|
+
out1 = self._convert_output(s1_curr)
|
|
368
|
+
|
|
369
|
+
modulus = 1 << 64
|
|
370
|
+
if t0_curr == 1:
|
|
371
|
+
# t0=1, t1=0: final_cw = beta - out0 + out1
|
|
372
|
+
final_cw = (beta - out0 + out1) % modulus
|
|
373
|
+
else:
|
|
374
|
+
# t0=0, t1=1: final_cw = out0 - out1 - beta
|
|
375
|
+
final_cw = (out0 - out1 - beta) % modulus
|
|
376
|
+
|
|
377
|
+
# Serialize keys
|
|
378
|
+
# Key format: seed || control_bit || CW1 || CW2 || ... || CWn || final_cw
|
|
379
|
+
k0 = self._serialize_key(s0, t0, correction_words, final_cw)
|
|
380
|
+
k1 = self._serialize_key(s1, t1, correction_words, final_cw)
|
|
381
|
+
|
|
382
|
+
logger.debug("DPF gen complete: key_size=%d bytes", len(k0))
|
|
383
|
+
|
|
384
|
+
return k0, k1
|
|
385
|
+
|
|
386
|
+
def _serialize_key(
|
|
387
|
+
self,
|
|
388
|
+
seed: bytes,
|
|
389
|
+
control_bit: int,
|
|
390
|
+
correction_words: list,
|
|
391
|
+
final_cw: int
|
|
392
|
+
) -> bytes:
|
|
393
|
+
"""
|
|
394
|
+
Serialize a DPF key to bytes.
|
|
395
|
+
|
|
396
|
+
Parameters
|
|
397
|
+
----------
|
|
398
|
+
seed : bytes
|
|
399
|
+
Initial seed
|
|
400
|
+
control_bit : int
|
|
401
|
+
Initial control bit (0 or 1)
|
|
402
|
+
correction_words : list
|
|
403
|
+
List of (s_left, t_left, s_right, t_right) tuples
|
|
404
|
+
final_cw : int
|
|
405
|
+
Final correction word (64-bit integer)
|
|
406
|
+
|
|
407
|
+
Returns
|
|
408
|
+
-------
|
|
409
|
+
bytes
|
|
410
|
+
Serialized key
|
|
411
|
+
"""
|
|
412
|
+
parts = [KEY_VERSION.to_bytes(2, 'big'), seed, bytes([control_bit])]
|
|
413
|
+
|
|
414
|
+
for s_left, t_left, s_right, t_right in correction_words:
|
|
415
|
+
parts.append(s_left)
|
|
416
|
+
parts.append(bytes([t_left]))
|
|
417
|
+
parts.append(s_right)
|
|
418
|
+
parts.append(bytes([t_right]))
|
|
419
|
+
|
|
420
|
+
parts.append(final_cw.to_bytes(8, 'big'))
|
|
421
|
+
|
|
422
|
+
return b''.join(parts)
|
|
423
|
+
|
|
424
|
+
def _deserialize_key(self, key: bytes) -> tuple:
|
|
425
|
+
"""
|
|
426
|
+
Deserialize a DPF key from bytes.
|
|
427
|
+
|
|
428
|
+
Parameters
|
|
429
|
+
----------
|
|
430
|
+
key : bytes
|
|
431
|
+
Serialized key
|
|
432
|
+
|
|
433
|
+
Returns
|
|
434
|
+
-------
|
|
435
|
+
tuple
|
|
436
|
+
(seed, control_bit, correction_words, final_cw)
|
|
437
|
+
"""
|
|
438
|
+
offset = 0
|
|
439
|
+
|
|
440
|
+
# Read and validate version
|
|
441
|
+
version = int.from_bytes(key[offset:offset + 2], 'big')
|
|
442
|
+
offset += 2
|
|
443
|
+
if version != KEY_VERSION:
|
|
444
|
+
raise ValueError(
|
|
445
|
+
f"Unsupported DPF key version {version}. Expected {KEY_VERSION}."
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# Read initial seed
|
|
449
|
+
seed = key[offset:offset + self.lambda_bytes]
|
|
450
|
+
offset += self.lambda_bytes
|
|
451
|
+
|
|
452
|
+
# Read initial control bit
|
|
453
|
+
control_bit = key[offset]
|
|
454
|
+
offset += 1
|
|
455
|
+
|
|
456
|
+
# Read correction words
|
|
457
|
+
correction_words = []
|
|
458
|
+
for _ in range(self.n):
|
|
459
|
+
s_left = key[offset:offset + self.lambda_bytes]
|
|
460
|
+
offset += self.lambda_bytes
|
|
461
|
+
t_left = key[offset]
|
|
462
|
+
offset += 1
|
|
463
|
+
s_right = key[offset:offset + self.lambda_bytes]
|
|
464
|
+
offset += self.lambda_bytes
|
|
465
|
+
t_right = key[offset]
|
|
466
|
+
offset += 1
|
|
467
|
+
correction_words.append((s_left, t_left, s_right, t_right))
|
|
468
|
+
|
|
469
|
+
# Read final correction word
|
|
470
|
+
final_cw = int.from_bytes(key[offset:offset + 8], 'big')
|
|
471
|
+
|
|
472
|
+
return seed, control_bit, correction_words, final_cw
|
|
473
|
+
|
|
474
|
+
def eval(self, sigma: int, key: bytes, x: int) -> int:
|
|
475
|
+
"""
|
|
476
|
+
Evaluate DPF at point x with key k_sigma.
|
|
477
|
+
|
|
478
|
+
Parameters
|
|
479
|
+
----------
|
|
480
|
+
sigma : int
|
|
481
|
+
Party index (0 or 1)
|
|
482
|
+
key : bytes
|
|
483
|
+
DPF key for party sigma
|
|
484
|
+
x : int
|
|
485
|
+
Point to evaluate (must be in [0, 2^n))
|
|
486
|
+
|
|
487
|
+
Returns
|
|
488
|
+
-------
|
|
489
|
+
int
|
|
490
|
+
The party's share of f_{α,β}(x). When combined:
|
|
491
|
+
eval(0, k0, x) + eval(1, k1, x) ≡ f_{α,β}(x) (mod 2^64)
|
|
492
|
+
|
|
493
|
+
Raises
|
|
494
|
+
------
|
|
495
|
+
ValueError
|
|
496
|
+
If sigma is not 0 or 1, or x is out of range
|
|
497
|
+
|
|
498
|
+
>>> dpf = DPF(security_param=128, domain_bits=4)
|
|
499
|
+
>>> k0, k1 = dpf.gen(alpha=10, beta=999)
|
|
500
|
+
>>> # At target point, shares sum to beta
|
|
501
|
+
>>> (dpf.eval(0, k0, 10) + dpf.eval(1, k1, 10)) % (2**64)
|
|
502
|
+
999
|
|
503
|
+
>>> # At other points, shares sum to 0
|
|
504
|
+
>>> (dpf.eval(0, k0, 5) + dpf.eval(1, k1, 5)) % (2**64)
|
|
505
|
+
0
|
|
506
|
+
"""
|
|
507
|
+
if sigma not in (0, 1):
|
|
508
|
+
raise ValueError("sigma must be 0 or 1")
|
|
509
|
+
if not (0 <= x < self.domain_size):
|
|
510
|
+
raise ValueError(f"x must be in [0, {self.domain_size})")
|
|
511
|
+
|
|
512
|
+
seed, t_curr, correction_words, final_cw = self._deserialize_key(key)
|
|
513
|
+
s_curr = seed
|
|
514
|
+
|
|
515
|
+
# Walk down tree to leaf x
|
|
516
|
+
for level in range(self.n):
|
|
517
|
+
x_bit = self._get_bit(x, level)
|
|
518
|
+
s_cw_left, t_cw_left, s_cw_right, t_cw_right = correction_words[level]
|
|
519
|
+
|
|
520
|
+
# Expand current seed
|
|
521
|
+
(s_left, t_left), (s_right, t_right) = self.prg(s_curr)
|
|
522
|
+
|
|
523
|
+
# Apply correction word if control bit is 1
|
|
524
|
+
if t_curr == 1:
|
|
525
|
+
s_left = xor_bytes(s_left, s_cw_left)
|
|
526
|
+
t_left ^= t_cw_left
|
|
527
|
+
s_right = xor_bytes(s_right, s_cw_right)
|
|
528
|
+
t_right ^= t_cw_right
|
|
529
|
+
|
|
530
|
+
# Move to child based on x_bit
|
|
531
|
+
if x_bit == 0:
|
|
532
|
+
s_curr, t_curr = s_left, t_left
|
|
533
|
+
else:
|
|
534
|
+
s_curr, t_curr = s_right, t_right
|
|
535
|
+
|
|
536
|
+
# Compute output share using (-1)^sigma factor for additive sharing
|
|
537
|
+
# Party 0: +convert(s), Party 1: -convert(s)
|
|
538
|
+
modulus = 1 << 64
|
|
539
|
+
base_out = self._convert_output(s_curr)
|
|
540
|
+
|
|
541
|
+
if sigma == 0:
|
|
542
|
+
out = base_out
|
|
543
|
+
else:
|
|
544
|
+
out = (-base_out) % modulus
|
|
545
|
+
|
|
546
|
+
# Apply final correction based on control bit
|
|
547
|
+
# Party 0 adds, Party 1 subtracts when their control bit is 1
|
|
548
|
+
# This ensures: when t0=t1=1, corrections cancel out
|
|
549
|
+
if t_curr == 1:
|
|
550
|
+
if sigma == 0:
|
|
551
|
+
out = (out + final_cw) % modulus
|
|
552
|
+
else:
|
|
553
|
+
out = (out - final_cw) % modulus
|
|
554
|
+
|
|
555
|
+
return out
|
|
556
|
+
|
|
557
|
+
def full_eval(self, sigma: int, key: bytes) -> List[int]:
|
|
558
|
+
"""
|
|
559
|
+
Evaluate DPF on entire domain [0, 2^n).
|
|
560
|
+
|
|
561
|
+
This is more efficient than calling eval() for each point,
|
|
562
|
+
as it reuses intermediate tree computations.
|
|
563
|
+
|
|
564
|
+
Parameters
|
|
565
|
+
----------
|
|
566
|
+
sigma : int
|
|
567
|
+
Party index (0 or 1)
|
|
568
|
+
key : bytes
|
|
569
|
+
DPF key for party sigma
|
|
570
|
+
|
|
571
|
+
Returns
|
|
572
|
+
-------
|
|
573
|
+
list
|
|
574
|
+
List of shares for all domain points. When combined:
|
|
575
|
+
full_eval(0, k0)[x] + full_eval(1, k1)[x] ≡ f_{α,β}(x) (mod 2^64)
|
|
576
|
+
|
|
577
|
+
Raises
|
|
578
|
+
------
|
|
579
|
+
ValueError
|
|
580
|
+
If sigma is not 0 or 1
|
|
581
|
+
|
|
582
|
+
>>> dpf = DPF(security_param=128, domain_bits=3)
|
|
583
|
+
>>> k0, k1 = dpf.gen(alpha=5, beta=777)
|
|
584
|
+
>>> out0 = dpf.full_eval(0, k0)
|
|
585
|
+
>>> out1 = dpf.full_eval(1, k1)
|
|
586
|
+
>>> # Check all positions
|
|
587
|
+
>>> all((out0[i] + out1[i]) % (2**64) == (777 if i == 5 else 0) for i in range(8))
|
|
588
|
+
True
|
|
589
|
+
"""
|
|
590
|
+
if sigma not in (0, 1):
|
|
591
|
+
raise ValueError("sigma must be 0 or 1")
|
|
592
|
+
|
|
593
|
+
seed, t_init, correction_words, final_cw = self._deserialize_key(key)
|
|
594
|
+
|
|
595
|
+
# Build tree level by level
|
|
596
|
+
# Each level stores list of (seed, control_bit) pairs
|
|
597
|
+
current_level = [(seed, t_init)]
|
|
598
|
+
|
|
599
|
+
for level in range(self.n):
|
|
600
|
+
s_cw_left, t_cw_left, s_cw_right, t_cw_right = correction_words[level]
|
|
601
|
+
next_level = []
|
|
602
|
+
|
|
603
|
+
for s_curr, t_curr in current_level:
|
|
604
|
+
# Expand current seed
|
|
605
|
+
(s_left, t_left), (s_right, t_right) = self.prg(s_curr)
|
|
606
|
+
|
|
607
|
+
# Apply correction word if control bit is 1
|
|
608
|
+
if t_curr == 1:
|
|
609
|
+
s_left = xor_bytes(s_left, s_cw_left)
|
|
610
|
+
t_left ^= t_cw_left
|
|
611
|
+
s_right = xor_bytes(s_right, s_cw_right)
|
|
612
|
+
t_right ^= t_cw_right
|
|
613
|
+
|
|
614
|
+
next_level.append((s_left, t_left))
|
|
615
|
+
next_level.append((s_right, t_right))
|
|
616
|
+
|
|
617
|
+
current_level = next_level
|
|
618
|
+
|
|
619
|
+
# Convert leaves to output shares using (-1)^sigma factor
|
|
620
|
+
modulus = 1 << 64
|
|
621
|
+
outputs = []
|
|
622
|
+
|
|
623
|
+
for s_leaf, t_leaf in current_level:
|
|
624
|
+
base_out = self._convert_output(s_leaf)
|
|
625
|
+
|
|
626
|
+
# Party 0: +convert(s), Party 1: -convert(s)
|
|
627
|
+
if sigma == 0:
|
|
628
|
+
out = base_out
|
|
629
|
+
else:
|
|
630
|
+
out = (-base_out) % modulus
|
|
631
|
+
|
|
632
|
+
# Apply final correction based on control bit
|
|
633
|
+
# Party 0 adds, Party 1 subtracts when their control bit is 1
|
|
634
|
+
if t_leaf == 1:
|
|
635
|
+
if sigma == 0:
|
|
636
|
+
out = (out + final_cw) % modulus
|
|
637
|
+
else:
|
|
638
|
+
out = (out - final_cw) % modulus
|
|
639
|
+
|
|
640
|
+
outputs.append(out)
|
|
641
|
+
|
|
642
|
+
return outputs
|