build(agent): weasel-1#856f80 iteration

This commit is contained in:
agent-856f80a92b1141b4 2026-04-24 20:55:47 +02:00
parent f52511d491
commit 59c7171f78
2 changed files with 123 additions and 0 deletions

View File

@ -1,6 +1,7 @@
import hashlib
import json
from typing import List
from .crypto import Signer
def _hash_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
@ -166,6 +167,52 @@ class DeltaLog:
digests = [e["digest"] for e in self.entries]
return merkle_root(digests)
def export_delta_block(self, since_index: int = 0, signer_key: bytes = None) -> dict:
"""Export a compact delta block suitable for DTN-friendly transfer.
The block contains:
- entries: full LedgerEntry dicts augmented with digest, delta_root, merkle_path
- digests: list of leaf digests included in the block (in order)
- root: merkle root for the block's leaves
- anchor: optional anchor recorded on this log
- aggregated_sig: HMAC-SHA256 of the root if signer_key is provided (hex)
This is a lightweight, backwards-compatible representation that
recipients can verify using verify_delta_root and (optional) aggregated_sig.
"""
leaves = [e["digest"] for e in self.entries]
# entries since index
entries = []
for i in range(since_index, len(self.entries)):
entry = self.entries[i]
path_info = merkle_path_for_index(leaves, i)
full_entry = entry["payload"].copy()
full_entry.update({
"digest": entry["digest"],
"delta_root": path_info["root"],
"merkle_path": path_info["path"],
"index": i,
})
entries.append(full_entry)
block_digests = [e["digest"] for e in self.entries[since_index:]]
block_root = merkle_root(block_digests)
aggregated_sig = None
if signer_key is not None:
signer = Signer(signer_key)
# sign the block root bytes
aggregated_sig = signer.sign(block_root.encode("utf-8")).hex()
return {
"entries": entries,
"digests": block_digests,
"root": block_root,
"anchor": self.anchor,
"aggregated_sig": aggregated_sig,
"since_index": since_index,
}
def checkpoint(self, prune_before_index: int = 0) -> dict:
"""Create a checkpoint for entries up to prune_before_index (exclusive) and optionally prune them.
@ -203,3 +250,39 @@ class DeltaLog:
if digest in cp.get("digests", []):
return True
return False
def verify_delta_block(block: dict, signer_key: bytes = None) -> bool:
"""Verify the integrity of a delta block.
Checks performed:
- recompute merkle root from provided digests and compare to block['root']
- ensure each entry's digest matches the corresponding digest
- if signer_key is provided, verify aggregated_sig matches HMAC of root
"""
if not block:
return False
digests = block.get("digests", [])
computed_root = merkle_root(digests)
if computed_root != (block.get("root") or ""):
return False
# ensure entries line up with digests
entries = block.get("entries", [])
for i, entry in enumerate(entries):
if entry.get("digest") != digests[i]:
return False
# verify optional aggregated signature
if signer_key is not None:
sig_hex = block.get("aggregated_sig")
if sig_hex is None:
return False
signer = Signer(signer_key)
expected = signer.sign((computed_root or "").encode("utf-8"))
try:
return expected.hex() == sig_hex
except Exception:
return False
return True

40
tests/test_delta_block.py Normal file
View File

@ -0,0 +1,40 @@
import sys
import pathlib
# Ensure repository root is on sys.path so tests can import the package
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
from cosmic_ledger.delta import DeltaLog, verify_delta_block
def test_export_and_verify_delta_block_with_signature():
dl = DeltaLog()
# add a few payloads
for i in range(4):
dl.add_entry({"v": i})
key = b'unique-test-key-xyz'
block = dl.export_delta_block(since_index=0, signer_key=key)
assert "root" in block
assert "digests" in block
assert "aggregated_sig" in block
assert block["aggregated_sig"] is not None
# verify should succeed with correct key
assert verify_delta_block(block, signer_key=key)
def test_verify_delta_block_tamper_detection():
dl = DeltaLog()
for i in range(3):
dl.add_entry({"v": i})
key = b'another-key-123'
block = dl.export_delta_block(since_index=0, signer_key=key)
# tamper with a digest
if block["digests"]:
block["digests"][0] = ("0" if block["digests"][0][0] != "0" else "1") + block["digests"][0][1:]
assert not verify_delta_block(block, signer_key=key)