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,800 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Numeric Attribute Encoding for CP-ABE
4
+
5
+ This module implements the "bag of bits" technique from the Bethencourt-Sahai-Waters
6
+ CP-ABE paper (IEEE S&P 2007) for representing numeric attributes and comparisons.
7
+
8
+ The technique converts numeric comparisons (e.g., age >= 21) into boolean attribute
9
+ expressions that can be evaluated using standard ABE schemes.
10
+
11
+ For an n-bit integer k, we create the following attributes:
12
+ - attr#bi#0 (bit i is 0)
13
+ - attr#bi#1 (bit i is 1)
14
+
15
+ Comparisons are then encoded as boolean expressions over these bit attributes.
16
+
17
+ Note: Uses '#' as delimiter instead of '_' because '_' is reserved for attribute
18
+ indexing in the PolicyParser.
19
+
20
+ Negation Limitation
21
+ -------------------
22
+ **Important**: The underlying Monotone Span Program (MSP) used in ABE schemes does
23
+ NOT support logical negation. This is a fundamental cryptographic limitation, not
24
+ an implementation limitation.
25
+
26
+ The PolicyParser's `!` prefix creates an attribute with `!` in its name (e.g., `!A`
27
+ becomes a literal attribute named "!A"), but this is NOT logical negation. To satisfy
28
+ a policy containing `!A`, the user must have an attribute literally named "!A".
29
+
30
+ For numeric comparisons, negation can be achieved through equivalent expressions:
31
+
32
+ NOT (age >= 21) --> age < 21
33
+ NOT (age > 21) --> age <= 21
34
+ NOT (age <= 21) --> age > 21
35
+ NOT (age < 21) --> age >= 21
36
+ NOT (age == 21) --> (age < 21) or (age > 21)
37
+
38
+ Use the `negate_comparison()` function to automatically convert negated comparisons
39
+ to their equivalent positive forms.
40
+
41
+ Example:
42
+ >>> from charm.toolbox.ABEnumeric import negate_comparison
43
+ >>> negate_comparison('age', '>=', 21)
44
+ ('age', '<', 21)
45
+ >>> negate_comparison('age', '==', 21) # Returns tuple for OR expression
46
+ (('age', '<', 21), ('age', '>', 21))
47
+ """
48
+
49
+ import re
50
+ import warnings
51
+
52
+
53
+ # Constants for validation
54
+ MIN_BITS = 1
55
+ MAX_BITS = 64 # Reasonable upper bound for bit width
56
+ RESERVED_PATTERN = re.compile(r'#b\d+#') # Pattern used in bit encoding
57
+
58
+
59
+ class NumericAttributeError(Exception):
60
+ """Base exception for numeric attribute encoding errors."""
61
+ pass
62
+
63
+
64
+ class BitOverflowError(NumericAttributeError):
65
+ """Raised when a value exceeds the representable range for the given bit width."""
66
+ pass
67
+
68
+
69
+ class InvalidBitWidthError(NumericAttributeError):
70
+ """Raised when an invalid bit width is specified."""
71
+ pass
72
+
73
+
74
+ class InvalidOperatorError(NumericAttributeError):
75
+ """Raised when an unsupported comparison operator is used."""
76
+ pass
77
+
78
+
79
+ class AttributeNameConflictError(NumericAttributeError):
80
+ """Raised when an attribute name conflicts with the bit encoding format."""
81
+ pass
82
+
83
+
84
+ def validate_num_bits(num_bits):
85
+ """
86
+ Validate the num_bits parameter.
87
+
88
+ Args:
89
+ num_bits: Number of bits for representation
90
+
91
+ Raises:
92
+ InvalidBitWidthError: If num_bits is invalid
93
+ """
94
+ if not isinstance(num_bits, int):
95
+ raise InvalidBitWidthError(f"num_bits must be an integer, got {type(num_bits).__name__}")
96
+ if num_bits < MIN_BITS:
97
+ raise InvalidBitWidthError(f"num_bits must be at least {MIN_BITS}, got {num_bits}")
98
+ if num_bits > MAX_BITS:
99
+ raise InvalidBitWidthError(f"num_bits must be at most {MAX_BITS}, got {num_bits}")
100
+
101
+
102
+ def validate_value(value, num_bits, context="value"):
103
+ """
104
+ Validate a numeric value for the given bit width.
105
+
106
+ Args:
107
+ value: The numeric value to validate
108
+ num_bits: Number of bits for representation
109
+ context: Description of the value for error messages
110
+
111
+ Raises:
112
+ ValueError: If value is negative
113
+ BitOverflowError: If value exceeds the bit width
114
+ """
115
+ if value < 0:
116
+ raise ValueError(f"Negative values not supported for {context}: {value}")
117
+
118
+ max_value = (1 << num_bits) - 1
119
+ if value > max_value:
120
+ raise BitOverflowError(
121
+ f"{context} {value} exceeds maximum representable value {max_value} "
122
+ f"for {num_bits}-bit encoding. Consider increasing num_bits."
123
+ )
124
+
125
+
126
+ def validate_attribute_name(attr_name):
127
+ """
128
+ Validate that an attribute name doesn't conflict with bit encoding format.
129
+
130
+ Args:
131
+ attr_name: The attribute name to validate
132
+
133
+ Raises:
134
+ AttributeNameConflictError: If the name conflicts with encoding format
135
+ """
136
+ if RESERVED_PATTERN.search(attr_name):
137
+ raise AttributeNameConflictError(
138
+ f"Attribute name '{attr_name}' contains reserved pattern '#b<digit>#' "
139
+ f"which conflicts with bit encoding format. Please rename the attribute."
140
+ )
141
+
142
+
143
+ def int_to_bits(value, num_bits=32):
144
+ """
145
+ Convert an integer to a list of bits (LSB first).
146
+
147
+ Args:
148
+ value: Non-negative integer to convert
149
+ num_bits: Number of bits in the representation
150
+
151
+ Returns:
152
+ List of bits (0 or 1), LSB first
153
+
154
+ Raises:
155
+ ValueError: If value is negative
156
+ BitOverflowError: If value exceeds bit width
157
+ InvalidBitWidthError: If num_bits is invalid
158
+ """
159
+ validate_num_bits(num_bits)
160
+ validate_value(value, num_bits, "value")
161
+
162
+ bits = []
163
+ for i in range(num_bits):
164
+ bits.append((value >> i) & 1)
165
+ return bits
166
+
167
+
168
+ def bits_to_attributes(attr_name, value, num_bits=32):
169
+ """
170
+ Convert a numeric value to a set of bit-level attributes.
171
+
172
+ For example, if attr_name='age' and value=5 (binary: 101), with num_bits=8:
173
+ Returns: {'age#b0#1', 'age#b1#0', 'age#b2#1', ...}
174
+
175
+ Uses '#' delimiter instead of '_' because '_' is reserved for attribute indexing
176
+ in the PolicyParser.
177
+
178
+ This is used when generating user attribute sets.
179
+
180
+ Raises:
181
+ AttributeNameConflictError: If attr_name conflicts with encoding format
182
+ ValueError: If value is negative
183
+ BitOverflowError: If value exceeds bit width
184
+ """
185
+ validate_attribute_name(attr_name)
186
+ bits = int_to_bits(value, num_bits)
187
+ attributes = set()
188
+ for i, bit in enumerate(bits):
189
+ attributes.add(f"{attr_name}#b{i}#{bit}")
190
+ return attributes
191
+
192
+
193
+ def encode_equality(attr_name, value, num_bits=32):
194
+ """
195
+ Encode 'attr == value' as a conjunction of bit attributes.
196
+
197
+ Returns the policy string representation.
198
+ For example: age == 5 (binary: 101) becomes:
199
+ (age#b0#1 and age#b1#0 and age#b2#1 and ...)
200
+ """
201
+ bits = int_to_bits(value, num_bits)
202
+ clauses = [f"{attr_name}#b{i}#{bit}" for i, bit in enumerate(bits)]
203
+ return " and ".join(clauses)
204
+
205
+
206
+ def encode_greater_than(attr_name, value, num_bits=32):
207
+ """
208
+ Encode 'attr > value' using bag of bits.
209
+
210
+ The encoding works by finding positions where the attribute can be strictly greater.
211
+ For each bit position i from high to low:
212
+ - If all higher bits match AND bit i of value is 0 AND bit i of attr is 1
213
+ OR a higher bit already made attr > value
214
+ """
215
+ bits = int_to_bits(value, num_bits)
216
+
217
+ # Build clauses for each bit position where attr can exceed value
218
+ or_clauses = []
219
+
220
+ for i in range(num_bits - 1, -1, -1): # high bit to low bit
221
+ if bits[i] == 0:
222
+ # If value's bit i is 0, attr > value if:
223
+ # - attr's bit i is 1 AND all higher bits are equal
224
+ higher_bits_match = [f"{attr_name}#b{j}#{bits[j]}"
225
+ for j in range(i + 1, num_bits)]
226
+ this_bit_greater = f"{attr_name}#b{i}#1"
227
+
228
+ if higher_bits_match:
229
+ clause = "(" + " and ".join(higher_bits_match + [this_bit_greater]) + ")"
230
+ else:
231
+ clause = this_bit_greater
232
+ or_clauses.append(clause)
233
+
234
+ if not or_clauses:
235
+ # value is all 1s, nothing can be greater (within num_bits)
236
+ return None
237
+
238
+ return " or ".join(or_clauses)
239
+
240
+
241
+ def encode_greater_than_or_equal(attr_name, value, num_bits=32):
242
+ """Encode 'attr >= value' as (attr > value - 1) or handle edge cases."""
243
+ if value == 0:
244
+ return None # Always true for non-negative
245
+
246
+ return encode_greater_than(attr_name, value - 1, num_bits)
247
+
248
+
249
+ def encode_less_than(attr_name, value, num_bits=32):
250
+ """
251
+ Encode 'attr < value' using bag of bits.
252
+
253
+ Similar to greater_than, but looking for positions where attr can be less.
254
+ """
255
+ if value == 0:
256
+ return None # Nothing is less than 0 for non-negative
257
+
258
+ bits = int_to_bits(value, num_bits)
259
+ or_clauses = []
260
+
261
+ for i in range(num_bits - 1, -1, -1):
262
+ if bits[i] == 1:
263
+ # If value's bit i is 1, attr < value if:
264
+ # - attr's bit i is 0 AND all higher bits are equal
265
+ higher_bits_match = [f"{attr_name}#b{j}#{bits[j]}"
266
+ for j in range(i + 1, num_bits)]
267
+ this_bit_less = f"{attr_name}#b{i}#0"
268
+
269
+ if higher_bits_match:
270
+ clause = "(" + " and ".join(higher_bits_match + [this_bit_less]) + ")"
271
+ else:
272
+ clause = this_bit_less
273
+ or_clauses.append(clause)
274
+
275
+ if not or_clauses:
276
+ return None
277
+
278
+ return " or ".join(or_clauses)
279
+
280
+
281
+ def encode_less_than_or_equal(attr_name, value, num_bits=32):
282
+ """Encode 'attr <= value' as (attr < value + 1)."""
283
+ return encode_less_than(attr_name, value + 1, num_bits)
284
+
285
+
286
+ # Supported comparison operators
287
+ SUPPORTED_OPERATORS = {'==', '>', '>=', '<', '<='}
288
+
289
+ # Mapping of operators to their logical negations
290
+ NEGATION_MAP = {
291
+ '>=': '<',
292
+ '>': '<=',
293
+ '<=': '>',
294
+ '<': '>=',
295
+ '==': None, # Special case: requires OR of two comparisons
296
+ }
297
+
298
+
299
+ def negate_comparison(attr_name, operator, value):
300
+ """
301
+ Convert a negated numeric comparison to its equivalent positive form.
302
+
303
+ Since Monotone Span Programs (MSP) used in ABE do not support logical
304
+ negation, this function converts negated comparisons to equivalent
305
+ positive expressions.
306
+
307
+ Args:
308
+ attr_name: The attribute name (e.g., 'age', 'level')
309
+ operator: The original operator to negate ('==', '>', '>=', '<', '<=')
310
+ value: The numeric value in the comparison
311
+
312
+ Returns:
313
+ For simple negations (>=, >, <=, <):
314
+ A tuple (attr_name, negated_operator, value)
315
+
316
+ For equality negation (==):
317
+ A tuple of two comparisons: ((attr_name, '<', value), (attr_name, '>', value))
318
+ These should be combined with OR in the policy.
319
+
320
+ Raises:
321
+ InvalidOperatorError: If operator is not supported
322
+
323
+ Examples:
324
+ >>> negate_comparison('age', '>=', 21)
325
+ ('age', '<', 21)
326
+
327
+ >>> negate_comparison('age', '>', 21)
328
+ ('age', '<=', 21)
329
+
330
+ >>> negate_comparison('age', '==', 21)
331
+ (('age', '<', 21), ('age', '>', 21))
332
+
333
+ Usage in policies:
334
+ # Instead of: NOT (age >= 21)
335
+ negated = negate_comparison('age', '>=', 21)
336
+ policy = f"{negated[0]} {negated[1]} {negated[2]}" # "age < 21"
337
+
338
+ # For equality negation:
339
+ negated = negate_comparison('age', '==', 21)
340
+ # Results in: (age < 21) or (age > 21)
341
+ policy = f"({negated[0][0]} {negated[0][1]} {negated[0][2]}) or ({negated[1][0]} {negated[1][1]} {negated[1][2]})"
342
+ """
343
+ if operator not in SUPPORTED_OPERATORS:
344
+ raise InvalidOperatorError(
345
+ f"Unsupported operator '{operator}'. "
346
+ f"Supported operators are: {', '.join(sorted(SUPPORTED_OPERATORS))}"
347
+ )
348
+
349
+ negated_op = NEGATION_MAP.get(operator)
350
+
351
+ if negated_op is not None:
352
+ # Simple negation: just flip the operator
353
+ return (attr_name, negated_op, value)
354
+ else:
355
+ # Equality negation: NOT (x == v) is (x < v) OR (x > v)
356
+ return ((attr_name, '<', value), (attr_name, '>', value))
357
+
358
+
359
+ def negate_comparison_to_policy(attr_name, operator, value):
360
+ """
361
+ Convert a negated numeric comparison directly to a policy string.
362
+
363
+ This is a convenience function that calls negate_comparison() and
364
+ formats the result as a policy string ready for use.
365
+
366
+ Args:
367
+ attr_name: The attribute name (e.g., 'age', 'level')
368
+ operator: The original operator to negate ('==', '>', '>=', '<', '<=')
369
+ value: The numeric value in the comparison
370
+
371
+ Returns:
372
+ A policy string representing the negated comparison.
373
+
374
+ Examples:
375
+ >>> negate_comparison_to_policy('age', '>=', 21)
376
+ 'age < 21'
377
+
378
+ >>> negate_comparison_to_policy('age', '==', 21)
379
+ '(age < 21) or (age > 21)'
380
+ """
381
+ result = negate_comparison(attr_name, operator, value)
382
+
383
+ if isinstance(result[0], tuple):
384
+ # Equality negation - two comparisons with OR
385
+ left = result[0]
386
+ right = result[1]
387
+ return f"({left[0]} {left[1]} {left[2]}) or ({right[0]} {right[1]} {right[2]})"
388
+ else:
389
+ # Simple negation
390
+ return f"{result[0]} {result[1]} {result[2]}"
391
+
392
+
393
+ def expand_numeric_comparison(attr_name, operator, value, num_bits=32):
394
+ """
395
+ Expand a numeric comparison into a boolean policy expression.
396
+
397
+ Args:
398
+ attr_name: The attribute name (e.g., 'age', 'level')
399
+ operator: One of '==', '>', '>=', '<', '<='
400
+ value: The numeric value to compare against
401
+ num_bits: Number of bits for the representation (default 32)
402
+
403
+ Returns:
404
+ A string policy expression using bit-level attributes
405
+
406
+ Raises:
407
+ InvalidOperatorError: If operator is not supported
408
+ AttributeNameConflictError: If attr_name conflicts with encoding format
409
+ ValueError: If value is negative
410
+ BitOverflowError: If value exceeds bit width
411
+ InvalidBitWidthError: If num_bits is invalid
412
+ """
413
+ # Validate operator
414
+ if operator not in SUPPORTED_OPERATORS:
415
+ raise InvalidOperatorError(
416
+ f"Unsupported operator '{operator}'. "
417
+ f"Supported operators are: {', '.join(sorted(SUPPORTED_OPERATORS))}"
418
+ )
419
+
420
+ # Validate attribute name
421
+ validate_attribute_name(attr_name)
422
+
423
+ # Validate num_bits
424
+ validate_num_bits(num_bits)
425
+
426
+ # Convert and validate value
427
+ try:
428
+ value = int(value)
429
+ except (ValueError, TypeError) as e:
430
+ raise ValueError(f"Cannot convert value to integer: {value}") from e
431
+
432
+ if value < 0:
433
+ raise ValueError(f"Negative values not supported: {value}")
434
+
435
+ # Check for potential overflow in <= comparison (value + 1)
436
+ max_value = (1 << num_bits) - 1
437
+ if operator == '<=' and value >= max_value:
438
+ # value + 1 would overflow, but <= max_value is always true for valid values
439
+ warnings.warn(
440
+ f"Comparison '{attr_name} <= {value}' with {num_bits}-bit encoding: "
441
+ f"value equals or exceeds max ({max_value}), result is always true for valid inputs.",
442
+ UserWarning
443
+ )
444
+ # Return a tautology
445
+ return f"{attr_name}#b0#0 or {attr_name}#b0#1"
446
+
447
+ # Check for overflow in the value itself (for other operators)
448
+ if value > max_value:
449
+ raise BitOverflowError(
450
+ f"Value {value} exceeds maximum representable value {max_value} "
451
+ f"for {num_bits}-bit encoding. Consider increasing num_bits."
452
+ )
453
+
454
+ if operator == '==':
455
+ return encode_equality(attr_name, value, num_bits)
456
+ elif operator == '>':
457
+ return encode_greater_than(attr_name, value, num_bits)
458
+ elif operator == '>=':
459
+ return encode_greater_than_or_equal(attr_name, value, num_bits)
460
+ elif operator == '<':
461
+ return encode_less_than(attr_name, value, num_bits)
462
+ elif operator == '<=':
463
+ return encode_less_than_or_equal(attr_name, value, num_bits)
464
+
465
+
466
+ # Regex pattern to match numeric comparisons in policies
467
+ # Matches: attr_name operator value (e.g., "age >= 21", "level>5")
468
+ # Note: Uses word boundary to avoid matching partial words
469
+ NUMERIC_PATTERN = re.compile(
470
+ r'\b([a-zA-Z][a-zA-Z0-9]*)\s*(==|>=|<=|>|<)\s*(\d+)\b'
471
+ )
472
+
473
+
474
+ def preprocess_numeric_policy(policy_str, num_bits=32, strict=False):
475
+ """
476
+ Preprocess a policy string to expand numeric comparisons.
477
+
478
+ Takes a policy like:
479
+ '(age >= 21 and clearance > 3) or admin'
480
+
481
+ And expands numeric comparisons into bit-level attributes:
482
+ '((age#b4#1 or ...) and (clearance#b...)) or admin'
483
+
484
+ Args:
485
+ policy_str: Original policy string with numeric comparisons
486
+ num_bits: Number of bits for numeric representation
487
+ strict: If True, raise exceptions on errors; if False, return original expression on error (default False)
488
+
489
+ Returns:
490
+ Expanded policy string with bit-level attributes
491
+
492
+ Raises:
493
+ ValueError: If policy_str is None
494
+ InvalidBitWidthError: If num_bits is invalid
495
+
496
+ Notes:
497
+ - Empty strings or whitespace-only strings return empty string
498
+ - Malformed expressions that don't match the pattern are left unchanged
499
+ - In non-strict mode, errors during expansion leave the original expression
500
+ """
501
+ # Validate inputs
502
+ if policy_str is None:
503
+ raise ValueError("policy_str cannot be None")
504
+
505
+ validate_num_bits(num_bits)
506
+
507
+ # Handle empty or whitespace-only strings
508
+ if not policy_str or policy_str.isspace():
509
+ return ""
510
+
511
+ errors = []
512
+
513
+ def replace_match(match):
514
+ attr_name = match.group(1)
515
+ operator = match.group(2)
516
+ value_str = match.group(3)
517
+ original = match.group(0)
518
+
519
+ try:
520
+ value = int(value_str)
521
+
522
+ # Check for attribute name conflicts
523
+ validate_attribute_name(attr_name)
524
+
525
+ expanded = expand_numeric_comparison(attr_name, operator, value, num_bits)
526
+ if expanded is None:
527
+ # Return a tautology or contradiction as appropriate
528
+ if operator == '>=' and value == 0:
529
+ # >= 0 is always true for non-negative
530
+ return f"({attr_name}#b0#0 or {attr_name}#b0#1)"
531
+ elif operator == '<' and value == 0:
532
+ # < 0 is always false for non-negative
533
+ # Return a contradiction (attribute AND its negation can't both be true)
534
+ # But since we can't use negation easily, we use a placeholder
535
+ warnings.warn(
536
+ f"Comparison '{attr_name} < 0' is always false for non-negative values",
537
+ UserWarning
538
+ )
539
+ return "FALSE"
540
+ elif operator == '>' and value == (1 << num_bits) - 1:
541
+ # > max_value is always false
542
+ warnings.warn(
543
+ f"Comparison '{attr_name} > {value}' is always false for {num_bits}-bit values",
544
+ UserWarning
545
+ )
546
+ return "FALSE"
547
+ return "FALSE" # placeholder for impossible conditions
548
+
549
+ # Wrap in parentheses to preserve operator precedence
550
+ return f"({expanded})"
551
+
552
+ except (NumericAttributeError, ValueError) as e:
553
+ errors.append((original, str(e)))
554
+ if strict:
555
+ raise
556
+ # In non-strict mode, leave the original expression unchanged
557
+ return original
558
+
559
+ result = NUMERIC_PATTERN.sub(replace_match, policy_str)
560
+
561
+ # Warn about any errors that occurred in non-strict mode
562
+ if errors and not strict:
563
+ for original, error in errors:
564
+ warnings.warn(
565
+ f"Failed to expand numeric comparison '{original}': {error}",
566
+ UserWarning
567
+ )
568
+
569
+ return result
570
+
571
+
572
+ def numeric_attributes_from_value(attr_name, value, num_bits=32):
573
+ """
574
+ Generate the attribute dictionary for a numeric attribute value.
575
+
576
+ This should be called when preparing user attributes for key generation.
577
+
578
+ Args:
579
+ attr_name: The attribute name (e.g., 'age')
580
+ value: The numeric value (e.g., 25)
581
+ num_bits: Number of bits for representation
582
+
583
+ Returns:
584
+ List of attribute strings like ['age#b0#1', 'age#b1#0', ...]
585
+ """
586
+ bits = int_to_bits(value, num_bits)
587
+ return [f"{attr_name}#b{i}#{bit}" for i, bit in enumerate(bits)]
588
+
589
+
590
+ class NumericAttributeHelper:
591
+ """
592
+ Helper class for working with numeric attributes in CP-ABE.
593
+
594
+ This class provides a high-level interface for:
595
+ - Expanding policies with numeric comparisons
596
+ - Converting numeric attribute values to bit representations
597
+
598
+ Usage:
599
+ helper = NumericAttributeHelper(num_bits=16) # 16-bit integers
600
+
601
+ # For encryption: expand the policy
602
+ policy = helper.expand_policy("age >= 21 and level > 5")
603
+
604
+ # For key generation: get user attributes
605
+ user_attrs = helper.user_attributes({'age': 25, 'level': 7, 'role': 'manager'})
606
+ # Returns: ['AGE#B0#1', 'AGE#B1#0', ..., 'LEVEL#B0#1', ..., 'MANAGER']
607
+
608
+ Attributes:
609
+ num_bits: Number of bits for numeric representation
610
+ max_value: Maximum representable value for the configured bit width
611
+ """
612
+
613
+ def __init__(self, num_bits=32, strict=False):
614
+ """
615
+ Initialize the helper with a specific bit width.
616
+
617
+ Args:
618
+ num_bits: Number of bits for numeric representation (default 32)
619
+ Use smaller values (e.g., 8, 16) for better performance
620
+ if your numeric ranges are limited.
621
+ strict: If True, raise exceptions on errors during policy expansion;
622
+ if False, leave problematic expressions unchanged (default: False)
623
+
624
+ Raises:
625
+ InvalidBitWidthError: If num_bits is invalid
626
+ """
627
+ validate_num_bits(num_bits)
628
+ self.num_bits = num_bits
629
+ self.max_value = (1 << num_bits) - 1
630
+ self.strict = strict
631
+
632
+ def expand_policy(self, policy_str):
633
+ """
634
+ Expand numeric comparisons in a policy string.
635
+
636
+ Args:
637
+ policy_str: Policy with numeric comparisons like "age >= 21"
638
+
639
+ Returns:
640
+ Expanded policy with bit-level attributes
641
+
642
+ Raises:
643
+ ValueError: If policy_str is None
644
+ NumericAttributeError: In strict mode, if expansion fails
645
+ """
646
+ return preprocess_numeric_policy(policy_str, self.num_bits, self.strict)
647
+
648
+ def user_attributes(self, attr_dict):
649
+ """
650
+ Convert a dictionary of user attributes to a list suitable for ABE.
651
+
652
+ Numeric values are converted to bit representations.
653
+ String values are uppercased as per standard attribute handling.
654
+
655
+ Args:
656
+ attr_dict: Dictionary mapping attribute names to values
657
+ e.g., {'age': 25, 'role': 'admin', 'level': 5}
658
+
659
+ Returns:
660
+ List of attribute strings for key generation
661
+
662
+ Raises:
663
+ ValueError: If a numeric value is negative
664
+ BitOverflowError: If a numeric value exceeds the bit width
665
+ AttributeNameConflictError: If an attribute name conflicts with encoding
666
+ """
667
+ if attr_dict is None:
668
+ raise ValueError("attr_dict cannot be None")
669
+
670
+ result = []
671
+
672
+ for name, value in attr_dict.items():
673
+ if isinstance(value, int):
674
+ # Validate the value
675
+ if value < 0:
676
+ raise ValueError(f"Negative value not supported for attribute '{name}': {value}")
677
+ if value > self.max_value:
678
+ raise BitOverflowError(
679
+ f"Value {value} for attribute '{name}' exceeds maximum {self.max_value} "
680
+ f"for {self.num_bits}-bit encoding"
681
+ )
682
+ # Validate attribute name
683
+ validate_attribute_name(name)
684
+ # Numeric attribute - convert to bits (uppercase to match parser)
685
+ attrs = numeric_attributes_from_value(name, value, self.num_bits)
686
+ result.extend([a.upper() for a in attrs])
687
+ elif isinstance(value, str):
688
+ # String attribute - uppercase
689
+ result.append(value.upper())
690
+ else:
691
+ # Convert to string and uppercase
692
+ result.append(str(value).upper())
693
+
694
+ return result
695
+
696
+ def check_satisfaction(self, user_attrs, required_comparison, attr_name, operator, value):
697
+ """
698
+ Check if a user's numeric attribute satisfies a comparison.
699
+
700
+ This is a utility for testing/debugging.
701
+
702
+ Args:
703
+ user_attrs: Dict with user's attribute values
704
+ attr_name: Name of the numeric attribute
705
+ operator: Comparison operator
706
+ value: Comparison value
707
+
708
+ Returns:
709
+ True if the comparison is satisfied
710
+ """
711
+ if attr_name not in user_attrs:
712
+ return False
713
+
714
+ user_value = user_attrs[attr_name]
715
+
716
+ if operator == '==':
717
+ return user_value == value
718
+ elif operator == '>':
719
+ return user_value > value
720
+ elif operator == '>=':
721
+ return user_value >= value
722
+ elif operator == '<':
723
+ return user_value < value
724
+ elif operator == '<=':
725
+ return user_value <= value
726
+
727
+ return False
728
+
729
+ def negate_comparison(self, attr_name, operator, value):
730
+ """
731
+ Convert a negated numeric comparison to its equivalent positive form.
732
+
733
+ This is a convenience wrapper around the module-level negate_comparison()
734
+ function.
735
+
736
+ Args:
737
+ attr_name: The attribute name (e.g., 'age', 'level')
738
+ operator: The original operator to negate ('==', '>', '>=', '<', '<=')
739
+ value: The numeric value in the comparison
740
+
741
+ Returns:
742
+ For simple negations: (attr_name, negated_operator, value)
743
+ For equality negation: ((attr_name, '<', value), (attr_name, '>', value))
744
+
745
+ Example:
746
+ >>> helper = NumericAttributeHelper(num_bits=8)
747
+ >>> helper.negate_comparison('age', '>=', 21)
748
+ ('age', '<', 21)
749
+ """
750
+ return negate_comparison(attr_name, operator, value)
751
+
752
+ def expand_negated_policy(self, attr_name, operator, value):
753
+ """
754
+ Expand a negated numeric comparison into a bit-level policy expression.
755
+
756
+ This method first negates the comparison, then expands it to bit-level
757
+ attributes.
758
+
759
+ Args:
760
+ attr_name: The attribute name (e.g., 'age', 'level')
761
+ operator: The original operator to negate ('==', '>', '>=', '<', '<=')
762
+ value: The numeric value in the comparison
763
+
764
+ Returns:
765
+ A policy string with bit-level attributes representing NOT (attr op value)
766
+
767
+ Example:
768
+ >>> helper = NumericAttributeHelper(num_bits=8)
769
+ >>> # NOT (age >= 21) becomes age < 21
770
+ >>> policy = helper.expand_negated_policy('age', '>=', 21)
771
+ >>> # Returns the bit-level encoding of age < 21
772
+ """
773
+ negated = negate_comparison(attr_name, operator, value)
774
+
775
+ if isinstance(negated[0], tuple):
776
+ # Equality negation - expand both parts and combine with OR
777
+ left = negated[0]
778
+ right = negated[1]
779
+ left_expanded = expand_numeric_comparison(
780
+ left[0], left[1], left[2], self.num_bits
781
+ )
782
+ right_expanded = expand_numeric_comparison(
783
+ right[0], right[1], right[2], self.num_bits
784
+ )
785
+
786
+ # Handle None returns (tautologies/contradictions)
787
+ if left_expanded is None and right_expanded is None:
788
+ return None
789
+ elif left_expanded is None:
790
+ return f"({right_expanded})"
791
+ elif right_expanded is None:
792
+ return f"({left_expanded})"
793
+ else:
794
+ return f"(({left_expanded}) or ({right_expanded}))"
795
+ else:
796
+ # Simple negation
797
+ return expand_numeric_comparison(
798
+ negated[0], negated[1], negated[2], self.num_bits
799
+ )
800
+