zeropx-layercomp 0.1.0__py3-none-any.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.
- layercomp/__init__.py +28 -0
- layercomp/__main__.py +4 -0
- layercomp/cli.py +131 -0
- layercomp/composition.py +149 -0
- zeropx_layercomp-0.1.0.dist-info/METADATA +161 -0
- zeropx_layercomp-0.1.0.dist-info/RECORD +9 -0
- zeropx_layercomp-0.1.0.dist-info/WHEEL +4 -0
- zeropx_layercomp-0.1.0.dist-info/entry_points.txt +2 -0
- zeropx_layercomp-0.1.0.dist-info/licenses/LICENSE +21 -0
layercomp/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""layercomp — algebraic composition of defense-in-depth layers.
|
|
2
|
+
|
|
3
|
+
Resolves the min-vs-sum-dB tension observed across security frameworks:
|
|
4
|
+
- ZK/cryptography → min (weakest-link adversarial model)
|
|
5
|
+
- EM shielding → sum-in-dB (passive independent attenuation)
|
|
6
|
+
- Consensus systems → max (honest-majority)
|
|
7
|
+
|
|
8
|
+
The correct operator depends on (threat_model, independence, correlation)
|
|
9
|
+
of each layer stack. Default is min (most conservative).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .composition import (
|
|
13
|
+
Layer,
|
|
14
|
+
ThreatModel,
|
|
15
|
+
ScoreUnit,
|
|
16
|
+
CompositionResult,
|
|
17
|
+
compose,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Layer",
|
|
23
|
+
"ThreatModel",
|
|
24
|
+
"ScoreUnit",
|
|
25
|
+
"CompositionResult",
|
|
26
|
+
"compose",
|
|
27
|
+
"__version__",
|
|
28
|
+
]
|
layercomp/__main__.py
ADDED
layercomp/cli.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""layercomp CLI."""
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from .composition import Layer, ThreatModel, ScoreUnit, compose
|
|
8
|
+
from . import __version__
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _load_stack(path: str) -> List[Layer]:
|
|
12
|
+
with open(path, encoding="utf-8") as f:
|
|
13
|
+
data = json.load(f)
|
|
14
|
+
layers = []
|
|
15
|
+
for l in data["layers"]:
|
|
16
|
+
layers.append(
|
|
17
|
+
Layer(
|
|
18
|
+
name=l["name"],
|
|
19
|
+
score=float(l["score"]),
|
|
20
|
+
unit=ScoreUnit(l.get("unit", "bits")),
|
|
21
|
+
threat_model=ThreatModel(l.get("threat_model", "adversarial")),
|
|
22
|
+
independent=bool(l.get("independent", True)),
|
|
23
|
+
notes=l.get("notes"),
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
return layers
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cmd_score(args) -> int:
|
|
30
|
+
layers = _load_stack(args.file)
|
|
31
|
+
result = compose(layers)
|
|
32
|
+
|
|
33
|
+
if args.format == "json":
|
|
34
|
+
out = {
|
|
35
|
+
"total": result.total,
|
|
36
|
+
"unit": result.unit.value,
|
|
37
|
+
"operator_used": result.operator_used,
|
|
38
|
+
"layer_that_determined": result.layer_that_determined,
|
|
39
|
+
"rationale": result.rationale,
|
|
40
|
+
"per_layer": result.per_layer,
|
|
41
|
+
"warnings": result.warnings,
|
|
42
|
+
}
|
|
43
|
+
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
44
|
+
else:
|
|
45
|
+
print(f"layercomp v{__version__}")
|
|
46
|
+
print()
|
|
47
|
+
print(f"Composition result")
|
|
48
|
+
print(f" operator: {result.operator_used}")
|
|
49
|
+
print(f" total: {result.total} {result.unit.value}")
|
|
50
|
+
if result.layer_that_determined:
|
|
51
|
+
print(f" determined: {result.layer_that_determined}")
|
|
52
|
+
print()
|
|
53
|
+
print(f"Rationale: {result.rationale}")
|
|
54
|
+
print()
|
|
55
|
+
print("Per-layer:")
|
|
56
|
+
for name, meta in result.per_layer.items():
|
|
57
|
+
marker = " * " if name == result.layer_that_determined else " "
|
|
58
|
+
note = f" — {meta['notes']}" if meta.get("notes") else ""
|
|
59
|
+
print(
|
|
60
|
+
f"{marker}{name}: {meta['score']} {meta['unit']} "
|
|
61
|
+
f"[{meta['threat_model']}, indep={meta['independent']}]{note}"
|
|
62
|
+
)
|
|
63
|
+
if result.warnings:
|
|
64
|
+
print()
|
|
65
|
+
print("Warnings:")
|
|
66
|
+
for w in result.warnings:
|
|
67
|
+
print(f" ! {w}")
|
|
68
|
+
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def cmd_explain(args) -> int:
|
|
73
|
+
"""Show the decision tree used by compose()."""
|
|
74
|
+
print(
|
|
75
|
+
"""layercomp decision tree
|
|
76
|
+
=======================
|
|
77
|
+
|
|
78
|
+
Given a stack of defense layers, the operator is chosen as follows:
|
|
79
|
+
|
|
80
|
+
1. If ANY layer has threat_model = ADVERSARIAL, or if threat models are mixed
|
|
81
|
+
→ use min (weakest-link, most conservative). This matches:
|
|
82
|
+
- ZK security: lambda_efetiva = min{lambda_alg, lambda_FRI, ...}
|
|
83
|
+
- Container escape: attacker needs to break just one namespace/cap
|
|
84
|
+
- Jurisdictional gates: one regulator denying is enough
|
|
85
|
+
|
|
86
|
+
2. If ALL layers are CONSENSUS
|
|
87
|
+
→ use max (honest-majority; system fails only if majority fails). Matches:
|
|
88
|
+
- BFT protocols
|
|
89
|
+
- Multi-sig (m-of-n where n-m+1 must fail)
|
|
90
|
+
|
|
91
|
+
3. If ALL layers are PASSIVE and all independent
|
|
92
|
+
→ use sum-in-dB (equivalent to product in linear). Matches:
|
|
93
|
+
- EM shielding: SE_total_dB = SE_ns + SE_cgroup + ...
|
|
94
|
+
- Statistical decorrelation of independent side channels
|
|
95
|
+
- Noise attenuation stages
|
|
96
|
+
|
|
97
|
+
4. If PASSIVE but layers are dependent
|
|
98
|
+
→ downgrade to min (with warning). Correlation invalidates additivity.
|
|
99
|
+
|
|
100
|
+
Notes
|
|
101
|
+
-----
|
|
102
|
+
- Mixed units emit a warning; composition assumes homogeneous units.
|
|
103
|
+
- "Independent" means: attacker cannot use one layer's weakness to bypass another.
|
|
104
|
+
- "Adversarial" is the default and safest assumption when in doubt.
|
|
105
|
+
"""
|
|
106
|
+
)
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def main() -> None:
|
|
111
|
+
parser = argparse.ArgumentParser(
|
|
112
|
+
prog="layercomp",
|
|
113
|
+
description="Compose defense layers with the correct algebraic operator (min/sum-dB/max)",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
116
|
+
subs = parser.add_subparsers(dest="command", required=True)
|
|
117
|
+
|
|
118
|
+
p_score = subs.add_parser("score", help="Compute composed score for a defense stack")
|
|
119
|
+
p_score.add_argument("file", help="Path to JSON file describing the layer stack")
|
|
120
|
+
p_score.add_argument("--format", choices=["text", "json"], default="text")
|
|
121
|
+
p_score.set_defaults(func=cmd_score)
|
|
122
|
+
|
|
123
|
+
p_explain = subs.add_parser("explain", help="Show the decision tree used by compose()")
|
|
124
|
+
p_explain.set_defaults(func=cmd_explain)
|
|
125
|
+
|
|
126
|
+
args = parser.parse_args()
|
|
127
|
+
sys.exit(args.func(args))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
main()
|
layercomp/composition.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Core composition algebra."""
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import List, Optional, Dict, Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ThreatModel(Enum):
|
|
8
|
+
ADVERSARIAL = "adversarial" # attacker chooses weakest link (crypto, security bounds)
|
|
9
|
+
PASSIVE = "passive" # random noise/leakage, all layers filter (EM shielding, statistical isolation)
|
|
10
|
+
CONSENSUS = "consensus" # need majority to fail (BFT, multi-sig, honest-majority)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ScoreUnit(Enum):
|
|
14
|
+
BITS = "bits" # cryptographic bit-security
|
|
15
|
+
DB = "dB" # decibels (10*log10 or 20*log10 of ratio)
|
|
16
|
+
LINEAR = "linear" # linear probability, factor, or dimensionless ratio
|
|
17
|
+
ORDINAL = "ordinal" # relative rank only, no arithmetic guaranteed
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Layer:
|
|
22
|
+
name: str
|
|
23
|
+
score: float
|
|
24
|
+
unit: ScoreUnit = ScoreUnit.BITS
|
|
25
|
+
threat_model: ThreatModel = ThreatModel.ADVERSARIAL
|
|
26
|
+
independent: bool = True
|
|
27
|
+
notes: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
def __post_init__(self):
|
|
30
|
+
if not isinstance(self.unit, ScoreUnit):
|
|
31
|
+
self.unit = ScoreUnit(self.unit)
|
|
32
|
+
if not isinstance(self.threat_model, ThreatModel):
|
|
33
|
+
self.threat_model = ThreatModel(self.threat_model)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class CompositionResult:
|
|
38
|
+
total: float
|
|
39
|
+
unit: ScoreUnit
|
|
40
|
+
operator_used: str
|
|
41
|
+
layer_that_determined: Optional[str]
|
|
42
|
+
per_layer: Dict[str, Dict[str, Any]]
|
|
43
|
+
warnings: List[str] = field(default_factory=list)
|
|
44
|
+
rationale: str = ""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def compose(layers: List[Layer], target_unit: Optional[ScoreUnit] = None) -> CompositionResult:
|
|
48
|
+
"""Compose defense layers into a single bound using the correct operator.
|
|
49
|
+
|
|
50
|
+
Decision tree:
|
|
51
|
+
- If any threat model is ADVERSARIAL, or mixed models → use min (most conservative)
|
|
52
|
+
- If all layers PASSIVE and all independent → use sum-in-dB (equivalent to product in linear)
|
|
53
|
+
- If all layers CONSENSUS → use max
|
|
54
|
+
- If dependent layers with passive/consensus model → downgrade to min with warning
|
|
55
|
+
"""
|
|
56
|
+
if not layers:
|
|
57
|
+
raise ValueError("no layers provided")
|
|
58
|
+
|
|
59
|
+
warnings: List[str] = []
|
|
60
|
+
threat_models = set(l.threat_model for l in layers)
|
|
61
|
+
units = set(l.unit for l in layers)
|
|
62
|
+
|
|
63
|
+
if len(units) > 1:
|
|
64
|
+
warnings.append(
|
|
65
|
+
f"mixed units {[u.value for u in units]} — composition assumes homogeneous units; "
|
|
66
|
+
"convert layers to a single unit before composition or interpret result cautiously"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if target_unit is None:
|
|
70
|
+
target_unit = layers[0].unit
|
|
71
|
+
|
|
72
|
+
# Determine operator
|
|
73
|
+
if ThreatModel.ADVERSARIAL in threat_models:
|
|
74
|
+
primary = ThreatModel.ADVERSARIAL
|
|
75
|
+
if len(threat_models) > 1:
|
|
76
|
+
warnings.append(
|
|
77
|
+
f"mixed threat models {[t.value for t in threat_models]} — falling back to adversarial (min)"
|
|
78
|
+
)
|
|
79
|
+
elif threat_models == {ThreatModel.CONSENSUS}:
|
|
80
|
+
primary = ThreatModel.CONSENSUS
|
|
81
|
+
elif threat_models == {ThreatModel.PASSIVE}:
|
|
82
|
+
all_indep = all(l.independent for l in layers)
|
|
83
|
+
if not all_indep:
|
|
84
|
+
warnings.append(
|
|
85
|
+
"passive threat model with dependent layers — additive composition unsafe; "
|
|
86
|
+
"falling back to min"
|
|
87
|
+
)
|
|
88
|
+
primary = ThreatModel.ADVERSARIAL
|
|
89
|
+
else:
|
|
90
|
+
primary = ThreatModel.PASSIVE
|
|
91
|
+
else:
|
|
92
|
+
primary = ThreatModel.ADVERSARIAL
|
|
93
|
+
|
|
94
|
+
# Apply operator
|
|
95
|
+
if primary == ThreatModel.ADVERSARIAL:
|
|
96
|
+
operator = "min"
|
|
97
|
+
determining = min(layers, key=lambda l: l.score)
|
|
98
|
+
total = determining.score
|
|
99
|
+
det_name = determining.name
|
|
100
|
+
rationale = (
|
|
101
|
+
"adversarial threat model — attacker selects weakest layer. "
|
|
102
|
+
"Composition is the minimum score across layers."
|
|
103
|
+
)
|
|
104
|
+
elif primary == ThreatModel.PASSIVE:
|
|
105
|
+
if target_unit == ScoreUnit.DB:
|
|
106
|
+
operator = "sum-in-dB"
|
|
107
|
+
total = sum(l.score for l in layers)
|
|
108
|
+
else:
|
|
109
|
+
operator = "sum-in-dB (raw)"
|
|
110
|
+
total = sum(l.score for l in layers)
|
|
111
|
+
warnings.append(
|
|
112
|
+
f"passive composition on non-dB unit ({target_unit.value}) is unusual; "
|
|
113
|
+
"consider recasting layers in dB — result reported as raw sum"
|
|
114
|
+
)
|
|
115
|
+
det_name = None
|
|
116
|
+
rationale = (
|
|
117
|
+
"passive threat model with independent layers — attenuations add. "
|
|
118
|
+
"In dB this is sum; in linear it would be product of factors."
|
|
119
|
+
)
|
|
120
|
+
else: # CONSENSUS
|
|
121
|
+
operator = "max"
|
|
122
|
+
determining = max(layers, key=lambda l: l.score)
|
|
123
|
+
total = determining.score
|
|
124
|
+
det_name = determining.name
|
|
125
|
+
rationale = (
|
|
126
|
+
"consensus / honest-majority model — system fails only when majority fails. "
|
|
127
|
+
"Composition is the maximum score across layers (strongest quorum member)."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
per_layer = {
|
|
131
|
+
l.name: {
|
|
132
|
+
"score": l.score,
|
|
133
|
+
"unit": l.unit.value,
|
|
134
|
+
"threat_model": l.threat_model.value,
|
|
135
|
+
"independent": l.independent,
|
|
136
|
+
"notes": l.notes,
|
|
137
|
+
}
|
|
138
|
+
for l in layers
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return CompositionResult(
|
|
142
|
+
total=total,
|
|
143
|
+
unit=target_unit,
|
|
144
|
+
operator_used=operator,
|
|
145
|
+
layer_that_determined=det_name,
|
|
146
|
+
per_layer=per_layer,
|
|
147
|
+
warnings=warnings,
|
|
148
|
+
rationale=rationale,
|
|
149
|
+
)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zeropx-layercomp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Algebraic composition of defense-in-depth layers — pick min, sum-dB, or max correctly
|
|
5
|
+
Project-URL: Homepage, https://github.com/NixEngine/zeropx-layercomp
|
|
6
|
+
Project-URL: Repository, https://github.com/NixEngine/zeropx-layercomp
|
|
7
|
+
Project-URL: Issues, https://github.com/NixEngine/zeropx-layercomp/issues
|
|
8
|
+
Author-email: "Dr. Vander Geraldo Rodrigues da Cunha Júnior" <vgrcjr@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: audit,composition,container,cryptography,defense-in-depth,security,zk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: Security :: Cryptography
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# zeropx-layercomp
|
|
32
|
+
|
|
33
|
+
**Algebraic composition of defense-in-depth layers.** Pick the right operator (min, sum-dB, or max) based on the threat model — not by habit.
|
|
34
|
+
|
|
35
|
+
## Why
|
|
36
|
+
|
|
37
|
+
There is a subtle but consequential inconsistency in how the literature composes layered defenses:
|
|
38
|
+
|
|
39
|
+
- **ZK / cryptographic security** uses `λ_efetiva = min{λ_alg, λ_FRI, λ_hash, ...}` — the weakest link.
|
|
40
|
+
- **EM shielding / passive attenuation** uses `SE_total_dB = SE_1 + SE_2 + ...` — additive in decibels (product in linear).
|
|
41
|
+
- **Consensus systems** use `max` — you fail only if the majority does.
|
|
42
|
+
|
|
43
|
+
The same word "defense-in-depth" describes all three, but the arithmetic is different, and the wrong choice can inflate a bound by orders of magnitude.
|
|
44
|
+
|
|
45
|
+
`layercomp` implements the decision tree and lets you feed a stack description and get the correctly composed score, with an explanation of which operator was used and why.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install zeropx-layercomp
|
|
51
|
+
```
|
|
52
|
+
*(Python import module: `layercomp` | CLI command: `layercomp`)*
|
|
53
|
+
|
|
54
|
+
Zero runtime dependencies. Python 3.10+.
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
Describe your stack in a JSON file:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"layers": [
|
|
63
|
+
{ "name": "arithmetization", "score": 128, "unit": "bits", "threat_model": "adversarial" },
|
|
64
|
+
{ "name": "FRI", "score": 100, "unit": "bits", "threat_model": "adversarial" },
|
|
65
|
+
{ "name": "Poseidon2", "score": 95, "unit": "bits", "threat_model": "adversarial",
|
|
66
|
+
"notes": "BBLP22 instance ~2^95, marked Unresolved by Least Authority" }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
layercomp score my-zk-stack.json
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Output:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
layercomp v0.1.0
|
|
81
|
+
|
|
82
|
+
Composition result
|
|
83
|
+
operator: min
|
|
84
|
+
total: 95 bits
|
|
85
|
+
determined: Poseidon2
|
|
86
|
+
|
|
87
|
+
Rationale: adversarial threat model — attacker selects weakest layer.
|
|
88
|
+
Composition is the minimum score across layers.
|
|
89
|
+
|
|
90
|
+
Per-layer:
|
|
91
|
+
arithmetization: 128 bits [adversarial, indep=True]
|
|
92
|
+
FRI: 100 bits [adversarial, indep=True]
|
|
93
|
+
* Poseidon2: 95 bits [adversarial, indep=True] — BBLP22 instance ~2^95, marked Unresolved by Least Authority
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Decision tree
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
layercomp explain
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Prints:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
1. Any layer ADVERSARIAL or mixed → min (most conservative)
|
|
106
|
+
2. All CONSENSUS → max (honest-majority)
|
|
107
|
+
3. All PASSIVE and all independent → sum-in-dB
|
|
108
|
+
4. PASSIVE but dependent → downgrade to min with warning
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## The common mistake this fixes
|
|
112
|
+
|
|
113
|
+
Two frameworks routinely conflate these operators:
|
|
114
|
+
|
|
115
|
+
- Container hardening scored with `SE_container = SE_ns + SE_cgroup + SE_seccomp + SE_net` is wrong under the adversarial model (attacker needs to escape *one* namespace, not all). It should be `min(...)`.
|
|
116
|
+
- Meanwhile, `sum-in-dB` is the right composition for a Faraday cage against a passive EM leakage — the field really is attenuated through every layer in series.
|
|
117
|
+
|
|
118
|
+
The distinction is `who is choosing the attack path`: an adversary chooses the weakest link; nature/noise flows through everything.
|
|
119
|
+
|
|
120
|
+
## As a library
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from layercomp import Layer, ThreatModel, ScoreUnit, compose
|
|
124
|
+
|
|
125
|
+
result = compose([
|
|
126
|
+
Layer("arithmetization", 128, ScoreUnit.BITS, ThreatModel.ADVERSARIAL),
|
|
127
|
+
Layer("FRI", 100, ScoreUnit.BITS, ThreatModel.ADVERSARIAL),
|
|
128
|
+
Layer("Poseidon2", 95, ScoreUnit.BITS, ThreatModel.ADVERSARIAL),
|
|
129
|
+
])
|
|
130
|
+
|
|
131
|
+
print(result.total) # 95
|
|
132
|
+
print(result.operator_used) # 'min'
|
|
133
|
+
print(result.layer_that_determined) # 'Poseidon2'
|
|
134
|
+
print(result.rationale) # human-readable explanation
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Testing
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pip install -e ".[test]"
|
|
141
|
+
python -m pytest tests/ -v
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Companion tools
|
|
145
|
+
|
|
146
|
+
`zeropx-layercomp` is part of the ZeroPx Auditor's Toolkit of zero-dependency Python packages:
|
|
147
|
+
|
|
148
|
+
- **zeropx-gguf-inspect** — static read-only inspection of GGUF model files without loading them
|
|
149
|
+
- **zeropx-corpus-scan** — byte-honest SHA-256 inventory of a document corpus
|
|
150
|
+
- **zeropx-receipt-chain** — append-only tamper-evident event ledger
|
|
151
|
+
- **zeropx-poseidon2-goldilocks** — pure-Python Poseidon2 reference implementation over Goldilocks
|
|
152
|
+
|
|
153
|
+
## Author & Provenance
|
|
154
|
+
|
|
155
|
+
**Sole Author:** Dr. Vander Geraldo Rodrigues da Cunha Júnior (`vgrcjr@gmail.com`).
|
|
156
|
+
|
|
157
|
+
*Technical & AI-Assisted Provenance Disclosure:* Auxiliary computational/LLM tooling was used strictly for boilerplate and test-harness scaffolding under 1:1 human architectural design, algebraic verification, code review, and sole human authorship. Zero runtime third-party or AI dependencies.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
layercomp/__init__.py,sha256=uYWWOlLKz9IuQ4qq8kr5dx29pVMk-EtDNlh_hKPz1DQ,698
|
|
2
|
+
layercomp/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
|
3
|
+
layercomp/cli.py,sha256=j0Lxy_CGXYpQQbDF4vUHRM0zrv37TQaMgniWiv7ipqE,4563
|
|
4
|
+
layercomp/composition.py,sha256=oaKJy2KeIMhzSRA8fx-okUIZcaojsOpilN0pbsBaZMQ,5341
|
|
5
|
+
zeropx_layercomp-0.1.0.dist-info/METADATA,sha256=3UPctnBXd9xTqKlXuj0Lnw0MivoTxf9geAAtpTohrFU,5765
|
|
6
|
+
zeropx_layercomp-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
7
|
+
zeropx_layercomp-0.1.0.dist-info/entry_points.txt,sha256=yzlSH2vPgflsIRfB9QGI8MLjhbH_KpALc8LBwosziiU,49
|
|
8
|
+
zeropx_layercomp-0.1.0.dist-info/licenses/LICENSE,sha256=SX_pPYQ-U0R-NtHc52RS7HRijupxgXsZya1ioFUdv2U,1102
|
|
9
|
+
zeropx_layercomp-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dr. Vander Geraldo Rodrigues da Cunha Júnior
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|