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.
Files changed (323) hide show
  1. charm/__init__.py +5 -0
  2. charm/adapters/__init__.py +0 -0
  3. charm/adapters/abenc_adapt_hybrid.py +90 -0
  4. charm/adapters/dabenc_adapt_hybrid.py +145 -0
  5. charm/adapters/ibenc_adapt_hybrid.py +72 -0
  6. charm/adapters/ibenc_adapt_identityhash.py +80 -0
  7. charm/adapters/kpabenc_adapt_hybrid.py +91 -0
  8. charm/adapters/pkenc_adapt_bchk05.py +121 -0
  9. charm/adapters/pkenc_adapt_chk04.py +91 -0
  10. charm/adapters/pkenc_adapt_hybrid.py +98 -0
  11. charm/adapters/pksig_adapt_naor01.py +89 -0
  12. charm/config.py +7 -0
  13. charm/core/__init__.py +0 -0
  14. charm/core/benchmark/benchmark_util.c +353 -0
  15. charm/core/benchmark/benchmark_util.h +61 -0
  16. charm/core/benchmark/benchmarkmodule.c +476 -0
  17. charm/core/benchmark/benchmarkmodule.h +162 -0
  18. charm/core/benchmark.cpython-313-darwin.so +0 -0
  19. charm/core/crypto/AES/AES.c +1464 -0
  20. charm/core/crypto/AES.cpython-313-darwin.so +0 -0
  21. charm/core/crypto/DES/DES.c +113 -0
  22. charm/core/crypto/DES.cpython-313-darwin.so +0 -0
  23. charm/core/crypto/DES3/DES3.c +26 -0
  24. charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
  25. charm/core/crypto/__init__.py +0 -0
  26. charm/core/crypto/cryptobase/XOR.c +80 -0
  27. charm/core/crypto/cryptobase/_counter.c +496 -0
  28. charm/core/crypto/cryptobase/_counter.h +54 -0
  29. charm/core/crypto/cryptobase/block_template.c +900 -0
  30. charm/core/crypto/cryptobase/block_template.h +69 -0
  31. charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
  32. charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
  33. charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
  34. charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
  35. charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
  36. charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
  37. charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
  38. charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
  39. charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
  40. charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
  41. charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
  42. charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
  43. charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
  44. charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
  45. charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
  46. charm/core/crypto/cryptobase/stream_template.c +271 -0
  47. charm/core/crypto/cryptobase/strxor.c +229 -0
  48. charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
  49. charm/core/engine/__init__.py +5 -0
  50. charm/core/engine/protocol.py +293 -0
  51. charm/core/engine/util.py +174 -0
  52. charm/core/math/__init__.py +0 -0
  53. charm/core/math/elliptic_curve/ecmodule.c +1986 -0
  54. charm/core/math/elliptic_curve/ecmodule.h +230 -0
  55. charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
  56. charm/core/math/elliptic_curve.pyi +63 -0
  57. charm/core/math/integer/integermodule.c +2539 -0
  58. charm/core/math/integer/integermodule.h +145 -0
  59. charm/core/math/integer.cpython-313-darwin.so +0 -0
  60. charm/core/math/integer.pyi +76 -0
  61. charm/core/math/pairing/miracl/miracl_config.h +37 -0
  62. charm/core/math/pairing/miracl/miracl_interface.h +118 -0
  63. charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
  64. charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
  65. charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
  66. charm/core/math/pairing/pairingmodule.c +2230 -0
  67. charm/core/math/pairing/pairingmodule.h +241 -0
  68. charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
  69. charm/core/math/pairing/relic/pairingmodule3.h +233 -0
  70. charm/core/math/pairing/relic/relic_interface.c +1337 -0
  71. charm/core/math/pairing/relic/relic_interface.h +217 -0
  72. charm/core/math/pairing/relic/test_relic.c +171 -0
  73. charm/core/math/pairing.cpython-313-darwin.so +0 -0
  74. charm/core/math/pairing.pyi +69 -0
  75. charm/core/utilities/base64.c +248 -0
  76. charm/core/utilities/base64.h +15 -0
  77. charm/schemes/__init__.py +0 -0
  78. charm/schemes/abenc/__init__.py +0 -0
  79. charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
  80. charm/schemes/abenc/abenc_bsw07.py +146 -0
  81. charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
  82. charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
  83. charm/schemes/abenc/abenc_lsw08.py +159 -0
  84. charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
  85. charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
  86. charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
  87. charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
  88. charm/schemes/abenc/abenc_waters09.py +144 -0
  89. charm/schemes/abenc/abenc_yct14.py +208 -0
  90. charm/schemes/abenc/abenc_yllc15.py +178 -0
  91. charm/schemes/abenc/ac17.py +248 -0
  92. charm/schemes/abenc/bsw07.py +141 -0
  93. charm/schemes/abenc/cgw15.py +277 -0
  94. charm/schemes/abenc/dabe_aw11.py +204 -0
  95. charm/schemes/abenc/dfa_fe12.py +144 -0
  96. charm/schemes/abenc/pk_hve08.py +179 -0
  97. charm/schemes/abenc/waters11.py +143 -0
  98. charm/schemes/aggrsign_MuSig.py +150 -0
  99. charm/schemes/aggrsign_bls.py +267 -0
  100. charm/schemes/blindsig_ps16.py +654 -0
  101. charm/schemes/chamhash_adm05.py +113 -0
  102. charm/schemes/chamhash_rsa_hw09.py +100 -0
  103. charm/schemes/commit/__init__.py +0 -0
  104. charm/schemes/commit/commit_gs08.py +77 -0
  105. charm/schemes/commit/commit_pedersen92.py +53 -0
  106. charm/schemes/encap_bchk05.py +62 -0
  107. charm/schemes/grpsig/__init__.py +0 -0
  108. charm/schemes/grpsig/groupsig_bgls04.py +114 -0
  109. charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
  110. charm/schemes/hibenc/__init__.py +0 -0
  111. charm/schemes/hibenc/hibenc_bb04.py +105 -0
  112. charm/schemes/hibenc/hibenc_lew11.py +193 -0
  113. charm/schemes/ibenc/__init__.py +0 -0
  114. charm/schemes/ibenc/clpkc_rp03.py +119 -0
  115. charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
  116. charm/schemes/ibenc/ibenc_bb03.py +94 -0
  117. charm/schemes/ibenc/ibenc_bf01.py +121 -0
  118. charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
  119. charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
  120. charm/schemes/ibenc/ibenc_lsw08.py +120 -0
  121. charm/schemes/ibenc/ibenc_sw05.py +238 -0
  122. charm/schemes/ibenc/ibenc_waters05.py +144 -0
  123. charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
  124. charm/schemes/ibenc/ibenc_waters09.py +107 -0
  125. charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
  126. charm/schemes/joye_scheme.py +106 -0
  127. charm/schemes/lem_scheme.py +207 -0
  128. charm/schemes/pk_fre_ccv11.py +107 -0
  129. charm/schemes/pk_vrf.py +127 -0
  130. charm/schemes/pkenc/__init__.py +0 -0
  131. charm/schemes/pkenc/pkenc_cs98.py +108 -0
  132. charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
  133. charm/schemes/pkenc/pkenc_gm82.py +98 -0
  134. charm/schemes/pkenc/pkenc_paillier99.py +118 -0
  135. charm/schemes/pkenc/pkenc_rabin.py +254 -0
  136. charm/schemes/pkenc/pkenc_rsa.py +186 -0
  137. charm/schemes/pksig/__init__.py +0 -0
  138. charm/schemes/pksig/pksig_CW13_z.py +135 -0
  139. charm/schemes/pksig/pksig_bls04.py +87 -0
  140. charm/schemes/pksig/pksig_boyen.py +156 -0
  141. charm/schemes/pksig/pksig_chch.py +97 -0
  142. charm/schemes/pksig/pksig_chp.py +70 -0
  143. charm/schemes/pksig/pksig_cl03.py +150 -0
  144. charm/schemes/pksig/pksig_cl04.py +87 -0
  145. charm/schemes/pksig/pksig_cllww12_z.py +142 -0
  146. charm/schemes/pksig/pksig_cyh.py +132 -0
  147. charm/schemes/pksig/pksig_dsa.py +76 -0
  148. charm/schemes/pksig/pksig_ecdsa.py +71 -0
  149. charm/schemes/pksig/pksig_hess.py +104 -0
  150. charm/schemes/pksig/pksig_hw.py +110 -0
  151. charm/schemes/pksig/pksig_lamport.py +63 -0
  152. charm/schemes/pksig/pksig_ps01.py +135 -0
  153. charm/schemes/pksig/pksig_ps02.py +124 -0
  154. charm/schemes/pksig/pksig_ps03.py +119 -0
  155. charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
  156. charm/schemes/pksig/pksig_schnorr91.py +77 -0
  157. charm/schemes/pksig/pksig_waters.py +115 -0
  158. charm/schemes/pksig/pksig_waters05.py +121 -0
  159. charm/schemes/pksig/pksig_waters09.py +121 -0
  160. charm/schemes/pre_mg07.py +150 -0
  161. charm/schemes/prenc/pre_afgh06.py +126 -0
  162. charm/schemes/prenc/pre_bbs98.py +123 -0
  163. charm/schemes/prenc/pre_nal16.py +216 -0
  164. charm/schemes/protocol_a01.py +272 -0
  165. charm/schemes/protocol_ao00.py +215 -0
  166. charm/schemes/protocol_cns07.py +274 -0
  167. charm/schemes/protocol_schnorr91.py +125 -0
  168. charm/schemes/sigma1.py +64 -0
  169. charm/schemes/sigma2.py +129 -0
  170. charm/schemes/sigma3.py +126 -0
  171. charm/schemes/threshold/__init__.py +59 -0
  172. charm/schemes/threshold/dkls23_dkg.py +556 -0
  173. charm/schemes/threshold/dkls23_presign.py +1089 -0
  174. charm/schemes/threshold/dkls23_sign.py +761 -0
  175. charm/schemes/threshold/xrpl_wallet.py +967 -0
  176. charm/test/__init__.py +0 -0
  177. charm/test/adapters/__init__.py +0 -0
  178. charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
  179. charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
  180. charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
  181. charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
  182. charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
  183. charm/test/benchmark/abenc_yllc15_bench.py +92 -0
  184. charm/test/benchmark/benchmark_test.py +148 -0
  185. charm/test/benchmark_threshold.py +260 -0
  186. charm/test/conftest.py +38 -0
  187. charm/test/fuzz/__init__.py +1 -0
  188. charm/test/fuzz/conftest.py +5 -0
  189. charm/test/fuzz/fuzz_policy_parser.py +76 -0
  190. charm/test/fuzz/fuzz_serialization.py +83 -0
  191. charm/test/schemes/__init__.py +0 -0
  192. charm/test/schemes/abenc/__init__.py +0 -0
  193. charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
  194. charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
  195. charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
  196. charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
  197. charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
  198. charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
  199. charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
  200. charm/test/schemes/chamhash_adm05_test.py +31 -0
  201. charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
  202. charm/test/schemes/commit/__init__.py +0 -0
  203. charm/test/schemes/commit/commit_gs08_test.py +24 -0
  204. charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
  205. charm/test/schemes/dabe_aw11_test.py +45 -0
  206. charm/test/schemes/encap_bchk05_test.py +21 -0
  207. charm/test/schemes/grpsig/__init__.py +0 -0
  208. charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
  209. charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
  210. charm/test/schemes/hibenc/__init__.py +0 -0
  211. charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
  212. charm/test/schemes/ibenc/__init__.py +0 -0
  213. charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
  214. charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
  215. charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
  216. charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
  217. charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
  218. charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
  219. charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
  220. charm/test/schemes/pk_vrf_test.py +29 -0
  221. charm/test/schemes/pkenc/__init__.py +0 -0
  222. charm/test/schemes/pkenc_test.py +255 -0
  223. charm/test/schemes/pksig/__init__.py +0 -0
  224. charm/test/schemes/pksig_test.py +376 -0
  225. charm/test/schemes/rsa_alg_test.py +340 -0
  226. charm/test/schemes/threshold_test.py +1792 -0
  227. charm/test/serialize/__init__.py +0 -0
  228. charm/test/serialize/serialize_test.py +40 -0
  229. charm/test/toolbox/__init__.py +0 -0
  230. charm/test/toolbox/conversion_test.py +30 -0
  231. charm/test/toolbox/ecgroup_test.py +53 -0
  232. charm/test/toolbox/integer_arithmetic_test.py +441 -0
  233. charm/test/toolbox/paddingschemes_test.py +238 -0
  234. charm/test/toolbox/policy_parser_stress_test.py +969 -0
  235. charm/test/toolbox/secretshare_test.py +28 -0
  236. charm/test/toolbox/symcrypto_test.py +108 -0
  237. charm/test/toolbox/test_policy_expression.py +16 -0
  238. charm/test/vectors/__init__.py +1 -0
  239. charm/test/vectors/test_bls_vectors.py +289 -0
  240. charm/test/vectors/test_pedersen_vectors.py +315 -0
  241. charm/test/vectors/test_schnorr_vectors.py +368 -0
  242. charm/test/zkp_compiler/__init__.py +9 -0
  243. charm/test/zkp_compiler/benchmark_zkp.py +258 -0
  244. charm/test/zkp_compiler/test_and_proof.py +240 -0
  245. charm/test/zkp_compiler/test_batch_verify.py +248 -0
  246. charm/test/zkp_compiler/test_dleq_proof.py +264 -0
  247. charm/test/zkp_compiler/test_or_proof.py +231 -0
  248. charm/test/zkp_compiler/test_proof_serialization.py +121 -0
  249. charm/test/zkp_compiler/test_range_proof.py +241 -0
  250. charm/test/zkp_compiler/test_representation_proof.py +325 -0
  251. charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
  252. charm/test/zkp_compiler/test_thread_safety.py +169 -0
  253. charm/test/zkp_compiler/test_zkp_parser.py +139 -0
  254. charm/toolbox/ABEnc.py +26 -0
  255. charm/toolbox/ABEncMultiAuth.py +66 -0
  256. charm/toolbox/ABEnumeric.py +800 -0
  257. charm/toolbox/Commit.py +24 -0
  258. charm/toolbox/DFA.py +89 -0
  259. charm/toolbox/FSA.py +1254 -0
  260. charm/toolbox/Hash.py +39 -0
  261. charm/toolbox/IBEnc.py +62 -0
  262. charm/toolbox/IBSig.py +64 -0
  263. charm/toolbox/PKEnc.py +66 -0
  264. charm/toolbox/PKSig.py +56 -0
  265. charm/toolbox/PREnc.py +32 -0
  266. charm/toolbox/ZKProof.py +289 -0
  267. charm/toolbox/__init__.py +0 -0
  268. charm/toolbox/bitstring.py +49 -0
  269. charm/toolbox/broadcast.py +220 -0
  270. charm/toolbox/conversion.py +100 -0
  271. charm/toolbox/eccurve.py +149 -0
  272. charm/toolbox/ecgroup.py +143 -0
  273. charm/toolbox/enum.py +60 -0
  274. charm/toolbox/hash_module.py +91 -0
  275. charm/toolbox/integergroup.py +323 -0
  276. charm/toolbox/iterate.py +22 -0
  277. charm/toolbox/matrixops.py +76 -0
  278. charm/toolbox/mpc_utils.py +296 -0
  279. charm/toolbox/msp.py +175 -0
  280. charm/toolbox/mta.py +985 -0
  281. charm/toolbox/node.py +120 -0
  282. charm/toolbox/ot/__init__.py +22 -0
  283. charm/toolbox/ot/base_ot.py +374 -0
  284. charm/toolbox/ot/dpf.py +642 -0
  285. charm/toolbox/ot/mpfss.py +228 -0
  286. charm/toolbox/ot/ot_extension.py +589 -0
  287. charm/toolbox/ot/silent_ot.py +378 -0
  288. charm/toolbox/paddingschemes.py +423 -0
  289. charm/toolbox/paddingschemes_test.py +238 -0
  290. charm/toolbox/pairingcurves.py +85 -0
  291. charm/toolbox/pairinggroup.py +186 -0
  292. charm/toolbox/policy_expression_spec.py +70 -0
  293. charm/toolbox/policytree.py +189 -0
  294. charm/toolbox/reCompiler.py +346 -0
  295. charm/toolbox/redundancyschemes.py +65 -0
  296. charm/toolbox/schemebase.py +188 -0
  297. charm/toolbox/secretshare.py +104 -0
  298. charm/toolbox/secretutil.py +174 -0
  299. charm/toolbox/securerandom.py +73 -0
  300. charm/toolbox/sigmaprotocol.py +46 -0
  301. charm/toolbox/specialprimes.py +45 -0
  302. charm/toolbox/symcrypto.py +279 -0
  303. charm/toolbox/threshold_sharing.py +553 -0
  304. charm/toolbox/xmlserialize.py +94 -0
  305. charm/toolbox/zknode.py +105 -0
  306. charm/zkp_compiler/__init__.py +89 -0
  307. charm/zkp_compiler/and_proof.py +460 -0
  308. charm/zkp_compiler/batch_verify.py +324 -0
  309. charm/zkp_compiler/dleq_proof.py +423 -0
  310. charm/zkp_compiler/or_proof.py +305 -0
  311. charm/zkp_compiler/range_proof.py +417 -0
  312. charm/zkp_compiler/representation_proof.py +466 -0
  313. charm/zkp_compiler/schnorr_proof.py +273 -0
  314. charm/zkp_compiler/thread_safe.py +150 -0
  315. charm/zkp_compiler/zk_demo.py +489 -0
  316. charm/zkp_compiler/zkp_factory.py +330 -0
  317. charm/zkp_compiler/zkp_generator.py +370 -0
  318. charm/zkp_compiler/zkparser.py +269 -0
  319. charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
  320. charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
  321. charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
  322. charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
  323. charm_crypto_framework-0.61.1.dist-info/top_level.txt +1 -0
@@ -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