tbz 0.1.0__tar.gz

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.
tbz-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: tbz
3
+ Version: 0.1.0
4
+ Summary: TBZ (TIBET-zip) — Block-level authenticated compression for the Zero-Trust era
5
+ Author-email: Jasper van de Meent <jasper@humotica.nl>
6
+ License: MIT OR Apache-2.0
7
+ Project-URL: Homepage, https://github.com/jaspertvdm/tbz
8
+ Project-URL: Repository, https://github.com/jaspertvdm/tbz
9
+ Project-URL: Issues, https://github.com/jaspertvdm/tbz/issues
10
+ Project-URL: Documentation, https://github.com/jaspertvdm/tbz/blob/main/ARCHITECTURE.md
11
+ Keywords: compression,security,provenance,supply-chain,zero-trust,ed25519
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Rust
24
+ Classifier: Topic :: Security
25
+ Classifier: Topic :: Security :: Cryptography
26
+ Classifier: Topic :: System :: Archiving :: Compression
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ Requires-Dist: requests>=2.28.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Requires-Dist: pytest-cov; extra == "dev"
33
+
34
+ # tbz — TIBET-zip for Python
35
+
36
+ **Block-level authenticated compression for the Zero-Trust era.**
37
+
38
+ Python client for TBZ archives and the Transparency Mirror network. Every block carries its own TIBET provenance envelope and Ed25519 signature. Tampered blocks are rejected before decompression touches memory.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install tbz
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ```python
49
+ from tbz import TBZArchive, Mirror
50
+
51
+ # Read and inspect a TBZ archive (pure Python, no binary needed)
52
+ archive = TBZArchive("release.tbz")
53
+ info = archive.inspect()
54
+ print(f"Blocks: {info['block_count']}, Hash: {info['content_hash']}")
55
+
56
+ # Verify integrity (uses Rust CLI if available, falls back to Python)
57
+ result = archive.verify()
58
+ print(result) # TBZ VERIFIED: 3 blocks (hash + Ed25519), 0 errors
59
+
60
+ # Look up in the Transparency Mirror (public, no auth needed)
61
+ mirror = Mirror()
62
+ entry = mirror.lookup("sha256:abc123...")
63
+ if entry:
64
+ print(f"Source: {entry['source_repo']}, Attestations: {len(entry['attestations'])}")
65
+
66
+ # Search by publisher
67
+ results = mirror.search(jis_id="jis:ed25519:77214ce9c262843e")
68
+
69
+ # Mirror stats
70
+ stats = mirror.stats()
71
+ print(f"Mirror node: {stats['node']}, entries: {stats['total_entries']}")
72
+ ```
73
+
74
+ ## With Rust CLI (full features)
75
+
76
+ For full Ed25519 signature verification and pack/unpack support, install the Rust binary:
77
+
78
+ ```bash
79
+ # From source
80
+ git clone https://github.com/jaspertvdm/tbz
81
+ cd tbz && cargo build --release
82
+ export PATH=$PATH:$(pwd)/target/release
83
+
84
+ # Then in Python
85
+ archive = TBZArchive("release.tbz")
86
+ result = archive.verify() # Full Ed25519 + SHA-256 verification
87
+ archive.unpack("./extracted") # Extract through TIBET Airlock
88
+ ```
89
+
90
+ ## Transparency Mirror
91
+
92
+ The Mirror is a distributed trust database for verifying TBZ package provenance. The bootstrap node runs at `brein.jaspervandemeent.nl`.
93
+
94
+ ```python
95
+ from tbz import Mirror
96
+
97
+ # Default: connects to bootstrap node
98
+ mirror = Mirror()
99
+
100
+ # Custom node
101
+ mirror = Mirror(node_url="https://your-mirror.example.com")
102
+
103
+ # Public endpoints (no auth)
104
+ mirror.lookup("sha256:...") # Look up by hash
105
+ mirror.search(verdict="safe") # Search attestations
106
+ mirror.stats() # Node statistics
107
+ ```
108
+
109
+ ## Links
110
+
111
+ - [GitHub](https://github.com/jaspertvdm/tbz)
112
+ - [Architecture](https://github.com/jaspertvdm/tbz/blob/main/ARCHITECTURE.md)
113
+ - [Mirror API](https://brein.jaspervandemeent.nl/api/tbz-mirror/stats)
114
+
115
+ ## License
116
+
117
+ MIT / Apache-2.0
tbz-0.1.0/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # tbz — TIBET-zip for Python
2
+
3
+ **Block-level authenticated compression for the Zero-Trust era.**
4
+
5
+ Python client for TBZ archives and the Transparency Mirror network. Every block carries its own TIBET provenance envelope and Ed25519 signature. Tampered blocks are rejected before decompression touches memory.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install tbz
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from tbz import TBZArchive, Mirror
17
+
18
+ # Read and inspect a TBZ archive (pure Python, no binary needed)
19
+ archive = TBZArchive("release.tbz")
20
+ info = archive.inspect()
21
+ print(f"Blocks: {info['block_count']}, Hash: {info['content_hash']}")
22
+
23
+ # Verify integrity (uses Rust CLI if available, falls back to Python)
24
+ result = archive.verify()
25
+ print(result) # TBZ VERIFIED: 3 blocks (hash + Ed25519), 0 errors
26
+
27
+ # Look up in the Transparency Mirror (public, no auth needed)
28
+ mirror = Mirror()
29
+ entry = mirror.lookup("sha256:abc123...")
30
+ if entry:
31
+ print(f"Source: {entry['source_repo']}, Attestations: {len(entry['attestations'])}")
32
+
33
+ # Search by publisher
34
+ results = mirror.search(jis_id="jis:ed25519:77214ce9c262843e")
35
+
36
+ # Mirror stats
37
+ stats = mirror.stats()
38
+ print(f"Mirror node: {stats['node']}, entries: {stats['total_entries']}")
39
+ ```
40
+
41
+ ## With Rust CLI (full features)
42
+
43
+ For full Ed25519 signature verification and pack/unpack support, install the Rust binary:
44
+
45
+ ```bash
46
+ # From source
47
+ git clone https://github.com/jaspertvdm/tbz
48
+ cd tbz && cargo build --release
49
+ export PATH=$PATH:$(pwd)/target/release
50
+
51
+ # Then in Python
52
+ archive = TBZArchive("release.tbz")
53
+ result = archive.verify() # Full Ed25519 + SHA-256 verification
54
+ archive.unpack("./extracted") # Extract through TIBET Airlock
55
+ ```
56
+
57
+ ## Transparency Mirror
58
+
59
+ The Mirror is a distributed trust database for verifying TBZ package provenance. The bootstrap node runs at `brein.jaspervandemeent.nl`.
60
+
61
+ ```python
62
+ from tbz import Mirror
63
+
64
+ # Default: connects to bootstrap node
65
+ mirror = Mirror()
66
+
67
+ # Custom node
68
+ mirror = Mirror(node_url="https://your-mirror.example.com")
69
+
70
+ # Public endpoints (no auth)
71
+ mirror.lookup("sha256:...") # Look up by hash
72
+ mirror.search(verdict="safe") # Search attestations
73
+ mirror.stats() # Node statistics
74
+ ```
75
+
76
+ ## Links
77
+
78
+ - [GitHub](https://github.com/jaspertvdm/tbz)
79
+ - [Architecture](https://github.com/jaspertvdm/tbz/blob/main/ARCHITECTURE.md)
80
+ - [Mirror API](https://brein.jaspervandemeent.nl/api/tbz-mirror/stats)
81
+
82
+ ## License
83
+
84
+ MIT / Apache-2.0
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tbz"
7
+ version = "0.1.0"
8
+ description = "TBZ (TIBET-zip) — Block-level authenticated compression for the Zero-Trust era"
9
+ readme = "README.md"
10
+ license = {text = "MIT OR Apache-2.0"}
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ {name = "Jasper van de Meent", email = "jasper@humotica.nl"},
14
+ ]
15
+ keywords = ["compression", "security", "provenance", "supply-chain", "zero-trust", "ed25519"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: System Administrators",
20
+ "License :: OSI Approved :: MIT License",
21
+ "License :: OSI Approved :: Apache Software License",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Rust",
29
+ "Topic :: Security",
30
+ "Topic :: Security :: Cryptography",
31
+ "Topic :: System :: Archiving :: Compression",
32
+ ]
33
+ dependencies = [
34
+ "requests>=2.28.0",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ dev = ["pytest>=7.0", "pytest-cov"]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/jaspertvdm/tbz"
42
+ Repository = "https://github.com/jaspertvdm/tbz"
43
+ Issues = "https://github.com/jaspertvdm/tbz/issues"
44
+ Documentation = "https://github.com/jaspertvdm/tbz/blob/main/ARCHITECTURE.md"
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["."]
48
+ include = ["tbz*"]
tbz-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ """
2
+ TBZ (TIBET-zip) — Block-level authenticated compression for the Zero-Trust era.
3
+
4
+ Every block carries its own TIBET provenance envelope and Ed25519 signature.
5
+ Invalid blocks are rejected before decompression touches memory.
6
+
7
+ Usage:
8
+ from tbz import TBZArchive, Mirror
9
+
10
+ # Verify an archive
11
+ archive = TBZArchive("release.tbz")
12
+ result = archive.verify()
13
+ print(result)
14
+
15
+ # Look up in Transparency Mirror
16
+ mirror = Mirror()
17
+ entry = mirror.lookup("sha256:abc123...")
18
+ """
19
+
20
+ __version__ = "0.1.0"
21
+ __author__ = "Jasper van de Meent"
22
+
23
+ from tbz.archive import TBZArchive
24
+ from tbz.mirror import Mirror
25
+
26
+ __all__ = ["TBZArchive", "Mirror", "__version__"]
@@ -0,0 +1,248 @@
1
+ """
2
+ TBZ Archive — Python interface for TBZ archives.
3
+
4
+ Wraps the Rust `tbz` CLI for pack/unpack/verify/inspect operations.
5
+ For native speed, install the Rust toolchain and build with `cargo build --release`.
6
+ """
7
+
8
+ import hashlib
9
+ import json
10
+ import os
11
+ import shutil
12
+ import struct
13
+ import subprocess
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+ from typing import List, Optional, Dict, Any
17
+
18
+
19
+ # TBZ magic bytes
20
+ MAGIC = bytes([0x54, 0x42, 0x5A])
21
+
22
+
23
+ @dataclass
24
+ class BlockInfo:
25
+ """Information about a single block in a TBZ archive."""
26
+ index: int
27
+ block_type: str # "Manifest", "Data", "Nested"
28
+ jis_level: int
29
+ compressed_size: int
30
+ uncompressed_size: int
31
+ content_hash: str
32
+ erachter: str # intent
33
+ signature_present: bool
34
+ path: Optional[str] = None
35
+
36
+
37
+ @dataclass
38
+ class VerifyResult:
39
+ """Result of verifying a TBZ archive."""
40
+ ok: bool
41
+ blocks_checked: int
42
+ errors: int
43
+ signing_key: Optional[str] = None
44
+ block_results: List[Dict[str, Any]] = field(default_factory=list)
45
+
46
+ def __str__(self):
47
+ status = "VERIFIED" if self.ok else "FAILED"
48
+ sig = f" (hash + Ed25519)" if self.signing_key else " (hash only)"
49
+ return f"TBZ {status}: {self.blocks_checked} blocks{sig}, {self.errors} errors"
50
+
51
+
52
+ class TBZArchive:
53
+ """Interface for TBZ archives.
54
+
55
+ Can operate in two modes:
56
+ 1. CLI mode: delegates to the Rust `tbz` binary (fast, full features)
57
+ 2. Pure Python mode: reads block headers directly (no external binary needed)
58
+
59
+ Args:
60
+ path: Path to the .tbz archive file
61
+ tbz_binary: Path to the tbz CLI binary (auto-detected if not specified)
62
+ """
63
+
64
+ def __init__(self, path: str, tbz_binary: str = None):
65
+ self.path = Path(path)
66
+ self._tbz_bin = tbz_binary or self._find_binary()
67
+
68
+ def _find_binary(self) -> Optional[str]:
69
+ """Try to find the tbz binary."""
70
+ # Check common locations
71
+ candidates = [
72
+ shutil.which("tbz"),
73
+ os.path.expanduser("~/.cargo/bin/tbz"),
74
+ str(Path(__file__).parent.parent.parent / "target" / "release" / "tbz"),
75
+ str(Path(__file__).parent.parent.parent / "target" / "debug" / "tbz"),
76
+ ]
77
+ for c in candidates:
78
+ if c and os.path.isfile(c) and os.access(c, os.X_OK):
79
+ return c
80
+ return None
81
+
82
+ @property
83
+ def exists(self) -> bool:
84
+ return self.path.exists()
85
+
86
+ def content_hash(self) -> str:
87
+ """Compute SHA-256 hash of the entire archive file."""
88
+ h = hashlib.sha256()
89
+ with open(self.path, "rb") as f:
90
+ for chunk in iter(lambda: f.read(8192), b""):
91
+ h.update(chunk)
92
+ return f"sha256:{h.hexdigest()}"
93
+
94
+ def read_blocks(self) -> List[BlockInfo]:
95
+ """Read block headers from the archive (pure Python, no CLI needed)."""
96
+ blocks = []
97
+ with open(self.path, "rb") as f:
98
+ while True:
99
+ magic = f.read(3)
100
+ if len(magic) < 3:
101
+ break
102
+ if magic != MAGIC:
103
+ break
104
+
105
+ # Read header
106
+ header_len = struct.unpack("<I", f.read(4))[0]
107
+ header_json = f.read(header_len)
108
+ header = json.loads(header_json)
109
+
110
+ # Read envelope
111
+ envelope_len = struct.unpack("<I", f.read(4))[0]
112
+ envelope_json = f.read(envelope_len)
113
+ envelope = json.loads(envelope_json)
114
+
115
+ # Read payload
116
+ payload_len = struct.unpack("<Q", f.read(8))[0]
117
+ f.seek(payload_len, 1) # skip payload
118
+
119
+ # Read signature (64 bytes)
120
+ sig = f.read(64)
121
+ sig_present = any(b != 0 for b in sig)
122
+
123
+ blocks.append(BlockInfo(
124
+ index=header.get("block_index", 0),
125
+ block_type=header.get("block_type", "Unknown"),
126
+ jis_level=header.get("jis_level", 0),
127
+ compressed_size=header.get("compressed_size", 0),
128
+ uncompressed_size=header.get("uncompressed_size", 0),
129
+ content_hash=envelope.get("erin", {}).get("content_hash", ""),
130
+ erachter=envelope.get("erachter", ""),
131
+ signature_present=sig_present,
132
+ path=None,
133
+ ))
134
+
135
+ return blocks
136
+
137
+ def verify(self) -> VerifyResult:
138
+ """Verify the archive integrity.
139
+
140
+ Uses the Rust CLI if available (fast, full Ed25519 verification).
141
+ Falls back to pure Python header reading if CLI not found.
142
+ """
143
+ if self._tbz_bin:
144
+ return self._verify_cli()
145
+ return self._verify_python()
146
+
147
+ def _verify_cli(self) -> VerifyResult:
148
+ """Verify using the Rust CLI binary."""
149
+ result = subprocess.run(
150
+ [self._tbz_bin, "verify", str(self.path)],
151
+ capture_output=True, text=True, timeout=60,
152
+ )
153
+
154
+ output = result.stdout + result.stderr
155
+ errors = output.count("FAIL")
156
+ blocks = output.count("] OK") + errors
157
+ signing_key = None
158
+
159
+ for line in output.splitlines():
160
+ if "Signing key:" in line:
161
+ signing_key = line.split("Ed25519")[-1].strip()
162
+
163
+ return VerifyResult(
164
+ ok=(errors == 0 and result.returncode == 0),
165
+ blocks_checked=blocks,
166
+ errors=errors,
167
+ signing_key=signing_key,
168
+ block_results=[{"raw_output": output}],
169
+ )
170
+
171
+ def _verify_python(self) -> VerifyResult:
172
+ """Basic verification using pure Python (header checks only)."""
173
+ blocks = self.read_blocks()
174
+ return VerifyResult(
175
+ ok=len(blocks) > 0,
176
+ blocks_checked=len(blocks),
177
+ errors=0,
178
+ signing_key=None,
179
+ block_results=[{"index": b.index, "type": b.block_type, "hash": b.content_hash} for b in blocks],
180
+ )
181
+
182
+ def inspect(self) -> Dict[str, Any]:
183
+ """Get detailed information about the archive."""
184
+ blocks = self.read_blocks()
185
+ return {
186
+ "path": str(self.path),
187
+ "size": self.path.stat().st_size,
188
+ "content_hash": self.content_hash(),
189
+ "block_count": len(blocks),
190
+ "blocks": [
191
+ {
192
+ "index": b.index,
193
+ "type": b.block_type,
194
+ "jis_level": b.jis_level,
195
+ "compressed_size": b.compressed_size,
196
+ "uncompressed_size": b.uncompressed_size,
197
+ "content_hash": b.content_hash,
198
+ "intent": b.erachter,
199
+ "signed": b.signature_present,
200
+ }
201
+ for b in blocks
202
+ ],
203
+ }
204
+
205
+ def unpack(self, output_dir: str = ".") -> bool:
206
+ """Extract the archive through the TIBET Airlock.
207
+
208
+ Requires the Rust CLI binary.
209
+ """
210
+ if not self._tbz_bin:
211
+ raise RuntimeError("tbz binary not found — install with: cargo install --path crates/tbz-cli")
212
+
213
+ result = subprocess.run(
214
+ [self._tbz_bin, "unpack", str(self.path), "-o", output_dir],
215
+ capture_output=True, text=True, timeout=120,
216
+ )
217
+ return result.returncode == 0
218
+
219
+ @staticmethod
220
+ def pack(source: str, output: str = "output.tbz", tbz_binary: str = None) -> "TBZArchive":
221
+ """Pack files into a TBZ archive.
222
+
223
+ Requires the Rust CLI binary.
224
+
225
+ Args:
226
+ source: Path to file or directory to archive
227
+ output: Output .tbz file path
228
+ tbz_binary: Path to tbz CLI binary
229
+
230
+ Returns:
231
+ TBZArchive instance for the created archive.
232
+ """
233
+ binary = tbz_binary or shutil.which("tbz")
234
+ if not binary:
235
+ raise RuntimeError("tbz binary not found — install with: cargo install --path crates/tbz-cli")
236
+
237
+ result = subprocess.run(
238
+ [binary, "pack", source, "-o", output],
239
+ capture_output=True, text=True, timeout=120,
240
+ )
241
+ if result.returncode != 0:
242
+ raise RuntimeError(f"Pack failed: {result.stderr}")
243
+
244
+ return TBZArchive(output, tbz_binary=binary)
245
+
246
+ def __repr__(self):
247
+ blocks = len(self.read_blocks()) if self.exists else 0
248
+ return f"TBZArchive({self.path!r}, blocks={blocks})"
@@ -0,0 +1,184 @@
1
+ """
2
+ TBZ Transparency Mirror client.
3
+
4
+ Query the distributed trust database for package provenance verification.
5
+ """
6
+
7
+ from typing import Optional, Dict, Any, List
8
+ import requests
9
+
10
+
11
+ DEFAULT_MIRROR = "https://brein.jaspervandemeent.nl"
12
+
13
+
14
+ class MirrorError(Exception):
15
+ """Error communicating with a Transparency Mirror node."""
16
+ pass
17
+
18
+
19
+ class Mirror:
20
+ """Client for the TBZ Transparency Mirror network.
21
+
22
+ The Mirror is a distributed trust database that stores content hashes,
23
+ Ed25519 signing keys, and attestations for TBZ archives.
24
+
25
+ Args:
26
+ node_url: Base URL of the mirror node (default: bootstrap node)
27
+ timeout: Request timeout in seconds
28
+ """
29
+
30
+ def __init__(self, node_url: str = DEFAULT_MIRROR, timeout: float = 10.0):
31
+ self.node_url = node_url.rstrip("/")
32
+ self.timeout = timeout
33
+ self._base = f"{self.node_url}/api/tbz-mirror"
34
+
35
+ def lookup(self, content_hash: str) -> Optional[Dict[str, Any]]:
36
+ """Look up a TBZ archive by its content hash.
37
+
38
+ Args:
39
+ content_hash: SHA-256 hash (format: "sha256:<hex>")
40
+
41
+ Returns:
42
+ Trust entry dict if found, None if not found.
43
+
44
+ Raises:
45
+ MirrorError: On network or server errors.
46
+ """
47
+ try:
48
+ resp = requests.get(
49
+ f"{self._base}/lookup/{content_hash}",
50
+ timeout=self.timeout,
51
+ )
52
+ if resp.status_code == 404:
53
+ return None
54
+ resp.raise_for_status()
55
+ return resp.json().get("entry")
56
+ except requests.ConnectionError as e:
57
+ raise MirrorError(f"Cannot reach mirror node {self.node_url}: {e}")
58
+ except requests.HTTPError as e:
59
+ raise MirrorError(f"Mirror error: {e}")
60
+
61
+ def register(self, content_hash: str, signing_key: str = None,
62
+ jis_id: str = None, source_repo: str = None,
63
+ block_count: int = 0, total_size: int = 0,
64
+ auth_token: str = None) -> Dict[str, Any]:
65
+ """Register a TBZ archive in the Transparency Mirror.
66
+
67
+ Requires authentication on the mirror node.
68
+
69
+ Args:
70
+ content_hash: SHA-256 hash of the archive
71
+ signing_key: Ed25519 public key (hex)
72
+ jis_id: JIS identity string
73
+ source_repo: Repository identifier
74
+ block_count: Number of blocks in the archive
75
+ total_size: Total uncompressed size in bytes
76
+ auth_token: JWT bearer token for authentication
77
+
78
+ Returns:
79
+ Registration result from the mirror.
80
+ """
81
+ headers = {"Content-Type": "application/json"}
82
+ if auth_token:
83
+ headers["Authorization"] = f"Bearer {auth_token}"
84
+
85
+ payload = {"content_hash": content_hash}
86
+ if signing_key:
87
+ payload["signing_key"] = signing_key
88
+ if jis_id:
89
+ payload["jis_id"] = jis_id
90
+ if source_repo:
91
+ payload["source_repo"] = source_repo
92
+ if block_count:
93
+ payload["block_count"] = block_count
94
+ if total_size:
95
+ payload["total_size"] = total_size
96
+
97
+ try:
98
+ resp = requests.post(
99
+ f"{self._base}/register",
100
+ json=payload,
101
+ headers=headers,
102
+ timeout=self.timeout,
103
+ )
104
+ resp.raise_for_status()
105
+ return resp.json()
106
+ except requests.HTTPError as e:
107
+ raise MirrorError(f"Registration failed: {e}")
108
+
109
+ def attest(self, content_hash: str, attester: str,
110
+ verdict: str = "safe", notes: str = None,
111
+ auth_token: str = None) -> Dict[str, Any]:
112
+ """Add an attestation to an existing entry.
113
+
114
+ Args:
115
+ content_hash: SHA-256 hash of the archive
116
+ attester: JIS ID or name of the attester
117
+ verdict: "safe", "suspicious", "malicious", or "unknown"
118
+ notes: Optional evidence/notes
119
+ auth_token: JWT bearer token for authentication
120
+
121
+ Returns:
122
+ Attestation result from the mirror.
123
+ """
124
+ headers = {"Content-Type": "application/json"}
125
+ if auth_token:
126
+ headers["Authorization"] = f"Bearer {auth_token}"
127
+
128
+ payload = {"attester": attester, "verdict": verdict}
129
+ if notes:
130
+ payload["notes"] = notes
131
+
132
+ try:
133
+ resp = requests.post(
134
+ f"{self._base}/attest/{content_hash}",
135
+ json=payload,
136
+ headers=headers,
137
+ timeout=self.timeout,
138
+ )
139
+ resp.raise_for_status()
140
+ return resp.json()
141
+ except requests.HTTPError as e:
142
+ raise MirrorError(f"Attestation failed: {e}")
143
+
144
+ def search(self, jis_id: str = None, verdict: str = None,
145
+ signing_key: str = None, limit: int = 50) -> List[Dict[str, Any]]:
146
+ """Search the Transparency Mirror.
147
+
148
+ All parameters are optional filters.
149
+
150
+ Returns:
151
+ List of matching trust entries.
152
+ """
153
+ params = {}
154
+ if jis_id:
155
+ params["jis_id"] = jis_id
156
+ if verdict:
157
+ params["verdict"] = verdict
158
+ if signing_key:
159
+ params["signing_key"] = signing_key
160
+ params["limit"] = limit
161
+
162
+ try:
163
+ resp = requests.get(
164
+ f"{self._base}/search",
165
+ params=params,
166
+ timeout=self.timeout,
167
+ )
168
+ resp.raise_for_status()
169
+ return resp.json().get("results", [])
170
+ except requests.HTTPError as e:
171
+ raise MirrorError(f"Search failed: {e}")
172
+
173
+ def stats(self) -> Dict[str, Any]:
174
+ """Get mirror node statistics.
175
+
176
+ Returns:
177
+ Dict with node info, entry counts, attestation counts.
178
+ """
179
+ resp = requests.get(f"{self._base}/stats", timeout=self.timeout)
180
+ resp.raise_for_status()
181
+ return resp.json()
182
+
183
+ def __repr__(self):
184
+ return f"Mirror(node={self.node_url!r})"
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: tbz
3
+ Version: 0.1.0
4
+ Summary: TBZ (TIBET-zip) — Block-level authenticated compression for the Zero-Trust era
5
+ Author-email: Jasper van de Meent <jasper@humotica.nl>
6
+ License: MIT OR Apache-2.0
7
+ Project-URL: Homepage, https://github.com/jaspertvdm/tbz
8
+ Project-URL: Repository, https://github.com/jaspertvdm/tbz
9
+ Project-URL: Issues, https://github.com/jaspertvdm/tbz/issues
10
+ Project-URL: Documentation, https://github.com/jaspertvdm/tbz/blob/main/ARCHITECTURE.md
11
+ Keywords: compression,security,provenance,supply-chain,zero-trust,ed25519
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Rust
24
+ Classifier: Topic :: Security
25
+ Classifier: Topic :: Security :: Cryptography
26
+ Classifier: Topic :: System :: Archiving :: Compression
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ Requires-Dist: requests>=2.28.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Requires-Dist: pytest-cov; extra == "dev"
33
+
34
+ # tbz — TIBET-zip for Python
35
+
36
+ **Block-level authenticated compression for the Zero-Trust era.**
37
+
38
+ Python client for TBZ archives and the Transparency Mirror network. Every block carries its own TIBET provenance envelope and Ed25519 signature. Tampered blocks are rejected before decompression touches memory.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install tbz
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ```python
49
+ from tbz import TBZArchive, Mirror
50
+
51
+ # Read and inspect a TBZ archive (pure Python, no binary needed)
52
+ archive = TBZArchive("release.tbz")
53
+ info = archive.inspect()
54
+ print(f"Blocks: {info['block_count']}, Hash: {info['content_hash']}")
55
+
56
+ # Verify integrity (uses Rust CLI if available, falls back to Python)
57
+ result = archive.verify()
58
+ print(result) # TBZ VERIFIED: 3 blocks (hash + Ed25519), 0 errors
59
+
60
+ # Look up in the Transparency Mirror (public, no auth needed)
61
+ mirror = Mirror()
62
+ entry = mirror.lookup("sha256:abc123...")
63
+ if entry:
64
+ print(f"Source: {entry['source_repo']}, Attestations: {len(entry['attestations'])}")
65
+
66
+ # Search by publisher
67
+ results = mirror.search(jis_id="jis:ed25519:77214ce9c262843e")
68
+
69
+ # Mirror stats
70
+ stats = mirror.stats()
71
+ print(f"Mirror node: {stats['node']}, entries: {stats['total_entries']}")
72
+ ```
73
+
74
+ ## With Rust CLI (full features)
75
+
76
+ For full Ed25519 signature verification and pack/unpack support, install the Rust binary:
77
+
78
+ ```bash
79
+ # From source
80
+ git clone https://github.com/jaspertvdm/tbz
81
+ cd tbz && cargo build --release
82
+ export PATH=$PATH:$(pwd)/target/release
83
+
84
+ # Then in Python
85
+ archive = TBZArchive("release.tbz")
86
+ result = archive.verify() # Full Ed25519 + SHA-256 verification
87
+ archive.unpack("./extracted") # Extract through TIBET Airlock
88
+ ```
89
+
90
+ ## Transparency Mirror
91
+
92
+ The Mirror is a distributed trust database for verifying TBZ package provenance. The bootstrap node runs at `brein.jaspervandemeent.nl`.
93
+
94
+ ```python
95
+ from tbz import Mirror
96
+
97
+ # Default: connects to bootstrap node
98
+ mirror = Mirror()
99
+
100
+ # Custom node
101
+ mirror = Mirror(node_url="https://your-mirror.example.com")
102
+
103
+ # Public endpoints (no auth)
104
+ mirror.lookup("sha256:...") # Look up by hash
105
+ mirror.search(verdict="safe") # Search attestations
106
+ mirror.stats() # Node statistics
107
+ ```
108
+
109
+ ## Links
110
+
111
+ - [GitHub](https://github.com/jaspertvdm/tbz)
112
+ - [Architecture](https://github.com/jaspertvdm/tbz/blob/main/ARCHITECTURE.md)
113
+ - [Mirror API](https://brein.jaspervandemeent.nl/api/tbz-mirror/stats)
114
+
115
+ ## License
116
+
117
+ MIT / Apache-2.0
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ tbz/__init__.py
4
+ tbz/archive.py
5
+ tbz/mirror.py
6
+ tbz.egg-info/PKG-INFO
7
+ tbz.egg-info/SOURCES.txt
8
+ tbz.egg-info/dependency_links.txt
9
+ tbz.egg-info/requires.txt
10
+ tbz.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ requests>=2.28.0
2
+
3
+ [dev]
4
+ pytest>=7.0
5
+ pytest-cov
@@ -0,0 +1 @@
1
+ tbz