charm-crypto-framework 0.61.1__cp313-cp313-macosx_10_13_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- charm/__init__.py +5 -0
- charm/adapters/__init__.py +0 -0
- charm/adapters/abenc_adapt_hybrid.py +90 -0
- charm/adapters/dabenc_adapt_hybrid.py +145 -0
- charm/adapters/ibenc_adapt_hybrid.py +72 -0
- charm/adapters/ibenc_adapt_identityhash.py +80 -0
- charm/adapters/kpabenc_adapt_hybrid.py +91 -0
- charm/adapters/pkenc_adapt_bchk05.py +121 -0
- charm/adapters/pkenc_adapt_chk04.py +91 -0
- charm/adapters/pkenc_adapt_hybrid.py +98 -0
- charm/adapters/pksig_adapt_naor01.py +89 -0
- charm/config.py +7 -0
- charm/core/__init__.py +0 -0
- charm/core/benchmark/benchmark_util.c +353 -0
- charm/core/benchmark/benchmark_util.h +61 -0
- charm/core/benchmark/benchmarkmodule.c +476 -0
- charm/core/benchmark/benchmarkmodule.h +162 -0
- charm/core/benchmark.cpython-313-darwin.so +0 -0
- charm/core/crypto/AES/AES.c +1464 -0
- charm/core/crypto/AES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES/DES.c +113 -0
- charm/core/crypto/DES.cpython-313-darwin.so +0 -0
- charm/core/crypto/DES3/DES3.c +26 -0
- charm/core/crypto/DES3.cpython-313-darwin.so +0 -0
- charm/core/crypto/__init__.py +0 -0
- charm/core/crypto/cryptobase/XOR.c +80 -0
- charm/core/crypto/cryptobase/_counter.c +496 -0
- charm/core/crypto/cryptobase/_counter.h +54 -0
- charm/core/crypto/cryptobase/block_template.c +900 -0
- charm/core/crypto/cryptobase/block_template.h +69 -0
- charm/core/crypto/cryptobase/cryptobasemodule.c +220 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_argchk.h +44 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cfg.h +186 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_cipher.h +941 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_custom.h +556 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_des.c +1912 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_hash.h +407 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_mac.h +496 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_macros.h +435 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_math.h +534 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_misc.h +103 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pk.h +653 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_pkcs.h +90 -0
- charm/core/crypto/cryptobase/libtom/tomcrypt_prng.h +199 -0
- charm/core/crypto/cryptobase/stream_template.c +271 -0
- charm/core/crypto/cryptobase/strxor.c +229 -0
- charm/core/crypto/cryptobase.cpython-313-darwin.so +0 -0
- charm/core/engine/__init__.py +5 -0
- charm/core/engine/protocol.py +293 -0
- charm/core/engine/util.py +174 -0
- charm/core/math/__init__.py +0 -0
- charm/core/math/elliptic_curve/ecmodule.c +1986 -0
- charm/core/math/elliptic_curve/ecmodule.h +230 -0
- charm/core/math/elliptic_curve.cpython-313-darwin.so +0 -0
- charm/core/math/elliptic_curve.pyi +63 -0
- charm/core/math/integer/integermodule.c +2539 -0
- charm/core/math/integer/integermodule.h +145 -0
- charm/core/math/integer.cpython-313-darwin.so +0 -0
- charm/core/math/integer.pyi +76 -0
- charm/core/math/pairing/miracl/miracl_config.h +37 -0
- charm/core/math/pairing/miracl/miracl_interface.h +118 -0
- charm/core/math/pairing/miracl/miracl_interface2.h +126 -0
- charm/core/math/pairing/miracl/pairingmodule2.c +2094 -0
- charm/core/math/pairing/miracl/pairingmodule2.h +307 -0
- charm/core/math/pairing/pairingmodule.c +2230 -0
- charm/core/math/pairing/pairingmodule.h +241 -0
- charm/core/math/pairing/relic/pairingmodule3.c +1853 -0
- charm/core/math/pairing/relic/pairingmodule3.h +233 -0
- charm/core/math/pairing/relic/relic_interface.c +1337 -0
- charm/core/math/pairing/relic/relic_interface.h +217 -0
- charm/core/math/pairing/relic/test_relic.c +171 -0
- charm/core/math/pairing.cpython-313-darwin.so +0 -0
- charm/core/math/pairing.pyi +69 -0
- charm/core/utilities/base64.c +248 -0
- charm/core/utilities/base64.h +15 -0
- charm/schemes/__init__.py +0 -0
- charm/schemes/abenc/__init__.py +0 -0
- charm/schemes/abenc/abenc_accountability_jyjxgd20.py +647 -0
- charm/schemes/abenc/abenc_bsw07.py +146 -0
- charm/schemes/abenc/abenc_ca_cpabe_ar17.py +684 -0
- charm/schemes/abenc/abenc_dacmacs_yj14.py +298 -0
- charm/schemes/abenc/abenc_lsw08.py +159 -0
- charm/schemes/abenc/abenc_maabe_rw15.py +236 -0
- charm/schemes/abenc/abenc_maabe_yj14.py +297 -0
- charm/schemes/abenc/abenc_tbpre_lww14.py +309 -0
- charm/schemes/abenc/abenc_unmcpabe_yahk14.py +223 -0
- charm/schemes/abenc/abenc_waters09.py +144 -0
- charm/schemes/abenc/abenc_yct14.py +208 -0
- charm/schemes/abenc/abenc_yllc15.py +178 -0
- charm/schemes/abenc/ac17.py +248 -0
- charm/schemes/abenc/bsw07.py +141 -0
- charm/schemes/abenc/cgw15.py +277 -0
- charm/schemes/abenc/dabe_aw11.py +204 -0
- charm/schemes/abenc/dfa_fe12.py +144 -0
- charm/schemes/abenc/pk_hve08.py +179 -0
- charm/schemes/abenc/waters11.py +143 -0
- charm/schemes/aggrsign_MuSig.py +150 -0
- charm/schemes/aggrsign_bls.py +267 -0
- charm/schemes/blindsig_ps16.py +654 -0
- charm/schemes/chamhash_adm05.py +113 -0
- charm/schemes/chamhash_rsa_hw09.py +100 -0
- charm/schemes/commit/__init__.py +0 -0
- charm/schemes/commit/commit_gs08.py +77 -0
- charm/schemes/commit/commit_pedersen92.py +53 -0
- charm/schemes/encap_bchk05.py +62 -0
- charm/schemes/grpsig/__init__.py +0 -0
- charm/schemes/grpsig/groupsig_bgls04.py +114 -0
- charm/schemes/grpsig/groupsig_bgls04_var.py +115 -0
- charm/schemes/hibenc/__init__.py +0 -0
- charm/schemes/hibenc/hibenc_bb04.py +105 -0
- charm/schemes/hibenc/hibenc_lew11.py +193 -0
- charm/schemes/ibenc/__init__.py +0 -0
- charm/schemes/ibenc/clpkc_rp03.py +119 -0
- charm/schemes/ibenc/ibenc_CW13_z.py +168 -0
- charm/schemes/ibenc/ibenc_bb03.py +94 -0
- charm/schemes/ibenc/ibenc_bf01.py +121 -0
- charm/schemes/ibenc/ibenc_ckrs09.py +120 -0
- charm/schemes/ibenc/ibenc_cllww12_z.py +172 -0
- charm/schemes/ibenc/ibenc_lsw08.py +120 -0
- charm/schemes/ibenc/ibenc_sw05.py +238 -0
- charm/schemes/ibenc/ibenc_waters05.py +144 -0
- charm/schemes/ibenc/ibenc_waters05_z.py +164 -0
- charm/schemes/ibenc/ibenc_waters09.py +107 -0
- charm/schemes/ibenc/ibenc_waters09_z.py +147 -0
- charm/schemes/joye_scheme.py +106 -0
- charm/schemes/lem_scheme.py +207 -0
- charm/schemes/pk_fre_ccv11.py +107 -0
- charm/schemes/pk_vrf.py +127 -0
- charm/schemes/pkenc/__init__.py +0 -0
- charm/schemes/pkenc/pkenc_cs98.py +108 -0
- charm/schemes/pkenc/pkenc_elgamal85.py +122 -0
- charm/schemes/pkenc/pkenc_gm82.py +98 -0
- charm/schemes/pkenc/pkenc_paillier99.py +118 -0
- charm/schemes/pkenc/pkenc_rabin.py +254 -0
- charm/schemes/pkenc/pkenc_rsa.py +186 -0
- charm/schemes/pksig/__init__.py +0 -0
- charm/schemes/pksig/pksig_CW13_z.py +135 -0
- charm/schemes/pksig/pksig_bls04.py +87 -0
- charm/schemes/pksig/pksig_boyen.py +156 -0
- charm/schemes/pksig/pksig_chch.py +97 -0
- charm/schemes/pksig/pksig_chp.py +70 -0
- charm/schemes/pksig/pksig_cl03.py +150 -0
- charm/schemes/pksig/pksig_cl04.py +87 -0
- charm/schemes/pksig/pksig_cllww12_z.py +142 -0
- charm/schemes/pksig/pksig_cyh.py +132 -0
- charm/schemes/pksig/pksig_dsa.py +76 -0
- charm/schemes/pksig/pksig_ecdsa.py +71 -0
- charm/schemes/pksig/pksig_hess.py +104 -0
- charm/schemes/pksig/pksig_hw.py +110 -0
- charm/schemes/pksig/pksig_lamport.py +63 -0
- charm/schemes/pksig/pksig_ps01.py +135 -0
- charm/schemes/pksig/pksig_ps02.py +124 -0
- charm/schemes/pksig/pksig_ps03.py +119 -0
- charm/schemes/pksig/pksig_rsa_hw09.py +206 -0
- charm/schemes/pksig/pksig_schnorr91.py +77 -0
- charm/schemes/pksig/pksig_waters.py +115 -0
- charm/schemes/pksig/pksig_waters05.py +121 -0
- charm/schemes/pksig/pksig_waters09.py +121 -0
- charm/schemes/pre_mg07.py +150 -0
- charm/schemes/prenc/pre_afgh06.py +126 -0
- charm/schemes/prenc/pre_bbs98.py +123 -0
- charm/schemes/prenc/pre_nal16.py +216 -0
- charm/schemes/protocol_a01.py +272 -0
- charm/schemes/protocol_ao00.py +215 -0
- charm/schemes/protocol_cns07.py +274 -0
- charm/schemes/protocol_schnorr91.py +125 -0
- charm/schemes/sigma1.py +64 -0
- charm/schemes/sigma2.py +129 -0
- charm/schemes/sigma3.py +126 -0
- charm/schemes/threshold/__init__.py +59 -0
- charm/schemes/threshold/dkls23_dkg.py +556 -0
- charm/schemes/threshold/dkls23_presign.py +1089 -0
- charm/schemes/threshold/dkls23_sign.py +761 -0
- charm/schemes/threshold/xrpl_wallet.py +967 -0
- charm/test/__init__.py +0 -0
- charm/test/adapters/__init__.py +0 -0
- charm/test/adapters/abenc_adapt_hybrid_test.py +29 -0
- charm/test/adapters/dabenc_adapt_hybrid_test.py +56 -0
- charm/test/adapters/ibenc_adapt_hybrid_test.py +36 -0
- charm/test/adapters/ibenc_adapt_identityhash_test.py +32 -0
- charm/test/adapters/kpabenc_adapt_hybrid_test.py +30 -0
- charm/test/benchmark/abenc_yllc15_bench.py +92 -0
- charm/test/benchmark/benchmark_test.py +148 -0
- charm/test/benchmark_threshold.py +260 -0
- charm/test/conftest.py +38 -0
- charm/test/fuzz/__init__.py +1 -0
- charm/test/fuzz/conftest.py +5 -0
- charm/test/fuzz/fuzz_policy_parser.py +76 -0
- charm/test/fuzz/fuzz_serialization.py +83 -0
- charm/test/schemes/__init__.py +0 -0
- charm/test/schemes/abenc/__init__.py +0 -0
- charm/test/schemes/abenc/abenc_bsw07_test.py +39 -0
- charm/test/schemes/abenc/abenc_dacmacs_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_lsw08_test.py +33 -0
- charm/test/schemes/abenc/abenc_maabe_yj14_test.py +16 -0
- charm/test/schemes/abenc/abenc_tbpre_lww14_test.py +16 -0
- charm/test/schemes/abenc/abenc_waters09_test.py +38 -0
- charm/test/schemes/abenc/abenc_yllc15_test.py +74 -0
- charm/test/schemes/chamhash_adm05_test.py +31 -0
- charm/test/schemes/chamhash_rsa_hw09_test.py +29 -0
- charm/test/schemes/commit/__init__.py +0 -0
- charm/test/schemes/commit/commit_gs08_test.py +24 -0
- charm/test/schemes/commit/commit_pedersen92_test.py +26 -0
- charm/test/schemes/dabe_aw11_test.py +45 -0
- charm/test/schemes/encap_bchk05_test.py +21 -0
- charm/test/schemes/grpsig/__init__.py +0 -0
- charm/test/schemes/grpsig/groupsig_bgls04_test.py +35 -0
- charm/test/schemes/grpsig/groupsig_bgls04_var_test.py +39 -0
- charm/test/schemes/hibenc/__init__.py +0 -0
- charm/test/schemes/hibenc/hibenc_bb04_test.py +28 -0
- charm/test/schemes/ibenc/__init__.py +0 -0
- charm/test/schemes/ibenc/ibenc_bb03_test.py +26 -0
- charm/test/schemes/ibenc/ibenc_bf01_test.py +24 -0
- charm/test/schemes/ibenc/ibenc_ckrs09_test.py +25 -0
- charm/test/schemes/ibenc/ibenc_lsw08_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_sw05_test.py +32 -0
- charm/test/schemes/ibenc/ibenc_waters05_test.py +31 -0
- charm/test/schemes/ibenc/ibenc_waters09_test.py +27 -0
- charm/test/schemes/pk_vrf_test.py +29 -0
- charm/test/schemes/pkenc/__init__.py +0 -0
- charm/test/schemes/pkenc_test.py +255 -0
- charm/test/schemes/pksig/__init__.py +0 -0
- charm/test/schemes/pksig_test.py +376 -0
- charm/test/schemes/rsa_alg_test.py +340 -0
- charm/test/schemes/threshold_test.py +1792 -0
- charm/test/serialize/__init__.py +0 -0
- charm/test/serialize/serialize_test.py +40 -0
- charm/test/toolbox/__init__.py +0 -0
- charm/test/toolbox/conversion_test.py +30 -0
- charm/test/toolbox/ecgroup_test.py +53 -0
- charm/test/toolbox/integer_arithmetic_test.py +441 -0
- charm/test/toolbox/paddingschemes_test.py +238 -0
- charm/test/toolbox/policy_parser_stress_test.py +969 -0
- charm/test/toolbox/secretshare_test.py +28 -0
- charm/test/toolbox/symcrypto_test.py +108 -0
- charm/test/toolbox/test_policy_expression.py +16 -0
- charm/test/vectors/__init__.py +1 -0
- charm/test/vectors/test_bls_vectors.py +289 -0
- charm/test/vectors/test_pedersen_vectors.py +315 -0
- charm/test/vectors/test_schnorr_vectors.py +368 -0
- charm/test/zkp_compiler/__init__.py +9 -0
- charm/test/zkp_compiler/benchmark_zkp.py +258 -0
- charm/test/zkp_compiler/test_and_proof.py +240 -0
- charm/test/zkp_compiler/test_batch_verify.py +248 -0
- charm/test/zkp_compiler/test_dleq_proof.py +264 -0
- charm/test/zkp_compiler/test_or_proof.py +231 -0
- charm/test/zkp_compiler/test_proof_serialization.py +121 -0
- charm/test/zkp_compiler/test_range_proof.py +241 -0
- charm/test/zkp_compiler/test_representation_proof.py +325 -0
- charm/test/zkp_compiler/test_schnorr_proof.py +221 -0
- charm/test/zkp_compiler/test_thread_safety.py +169 -0
- charm/test/zkp_compiler/test_zkp_parser.py +139 -0
- charm/toolbox/ABEnc.py +26 -0
- charm/toolbox/ABEncMultiAuth.py +66 -0
- charm/toolbox/ABEnumeric.py +800 -0
- charm/toolbox/Commit.py +24 -0
- charm/toolbox/DFA.py +89 -0
- charm/toolbox/FSA.py +1254 -0
- charm/toolbox/Hash.py +39 -0
- charm/toolbox/IBEnc.py +62 -0
- charm/toolbox/IBSig.py +64 -0
- charm/toolbox/PKEnc.py +66 -0
- charm/toolbox/PKSig.py +56 -0
- charm/toolbox/PREnc.py +32 -0
- charm/toolbox/ZKProof.py +289 -0
- charm/toolbox/__init__.py +0 -0
- charm/toolbox/bitstring.py +49 -0
- charm/toolbox/broadcast.py +220 -0
- charm/toolbox/conversion.py +100 -0
- charm/toolbox/eccurve.py +149 -0
- charm/toolbox/ecgroup.py +143 -0
- charm/toolbox/enum.py +60 -0
- charm/toolbox/hash_module.py +91 -0
- charm/toolbox/integergroup.py +323 -0
- charm/toolbox/iterate.py +22 -0
- charm/toolbox/matrixops.py +76 -0
- charm/toolbox/mpc_utils.py +296 -0
- charm/toolbox/msp.py +175 -0
- charm/toolbox/mta.py +985 -0
- charm/toolbox/node.py +120 -0
- charm/toolbox/ot/__init__.py +22 -0
- charm/toolbox/ot/base_ot.py +374 -0
- charm/toolbox/ot/dpf.py +642 -0
- charm/toolbox/ot/mpfss.py +228 -0
- charm/toolbox/ot/ot_extension.py +589 -0
- charm/toolbox/ot/silent_ot.py +378 -0
- charm/toolbox/paddingschemes.py +423 -0
- charm/toolbox/paddingschemes_test.py +238 -0
- charm/toolbox/pairingcurves.py +85 -0
- charm/toolbox/pairinggroup.py +186 -0
- charm/toolbox/policy_expression_spec.py +70 -0
- charm/toolbox/policytree.py +189 -0
- charm/toolbox/reCompiler.py +346 -0
- charm/toolbox/redundancyschemes.py +65 -0
- charm/toolbox/schemebase.py +188 -0
- charm/toolbox/secretshare.py +104 -0
- charm/toolbox/secretutil.py +174 -0
- charm/toolbox/securerandom.py +73 -0
- charm/toolbox/sigmaprotocol.py +46 -0
- charm/toolbox/specialprimes.py +45 -0
- charm/toolbox/symcrypto.py +279 -0
- charm/toolbox/threshold_sharing.py +553 -0
- charm/toolbox/xmlserialize.py +94 -0
- charm/toolbox/zknode.py +105 -0
- charm/zkp_compiler/__init__.py +89 -0
- charm/zkp_compiler/and_proof.py +460 -0
- charm/zkp_compiler/batch_verify.py +324 -0
- charm/zkp_compiler/dleq_proof.py +423 -0
- charm/zkp_compiler/or_proof.py +305 -0
- charm/zkp_compiler/range_proof.py +417 -0
- charm/zkp_compiler/representation_proof.py +466 -0
- charm/zkp_compiler/schnorr_proof.py +273 -0
- charm/zkp_compiler/thread_safe.py +150 -0
- charm/zkp_compiler/zk_demo.py +489 -0
- charm/zkp_compiler/zkp_factory.py +330 -0
- charm/zkp_compiler/zkp_generator.py +370 -0
- charm/zkp_compiler/zkparser.py +269 -0
- charm_crypto_framework-0.61.1.dist-info/METADATA +337 -0
- charm_crypto_framework-0.61.1.dist-info/RECORD +323 -0
- charm_crypto_framework-0.61.1.dist-info/WHEEL +5 -0
- charm_crypto_framework-0.61.1.dist-info/licenses/LICENSE.txt +165 -0
- charm_crypto_framework-0.61.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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
|
+
|