nedb-engine 2.2.31__tar.gz → 2.2.32__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.
Files changed (37) hide show
  1. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/PKG-INFO +3 -2
  2. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/README.md +2 -1
  3. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/pyproject.toml +1 -1
  4. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/__init__.py +3 -1
  5. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/_native.abi3.so +0 -0
  6. nedb_engine-2.2.32/python/nedb/nedbd-v2 +0 -0
  7. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/nedbd-v2-darwin-arm64 +0 -0
  8. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/nedbd-v2-darwin-x64 +0 -0
  9. nedb_engine-2.2.32/python/nedb/nedbd-v2.exe +0 -0
  10. nedb_engine-2.2.32/python/nedb/proof.py +126 -0
  11. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/server.py +52 -0
  12. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/rust/nedb-v2/README.md +3 -3
  13. nedb_engine-2.2.31/python/nedb/nedbd-v2 +0 -0
  14. nedb_engine-2.2.31/python/nedb/nedbd-v2.exe +0 -0
  15. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/.gitignore +0 -0
  16. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/LICENSE +0 -0
  17. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/client/node/README.md +0 -0
  18. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/client/python/README.md +0 -0
  19. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/autoindex.py +0 -0
  20. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/backends/__init__.py +0 -0
  21. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/backends/redis_backend.py +0 -0
  22. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/cascade.py +0 -0
  23. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/concurrent.py +0 -0
  24. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/crypto.py +0 -0
  25. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/engine.py +0 -0
  26. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/index.py +0 -0
  27. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/log.py +0 -0
  28. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/merkle.py +0 -0
  29. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/mongo.py +0 -0
  30. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/query.py +0 -0
  31. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/redis_compat.py +0 -0
  32. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/relations.py +0 -0
  33. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/resp2.py +0 -0
  34. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/snapshot.py +0 -0
  35. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/sql.py +0 -0
  36. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/store.py +0 -0
  37. {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/wrap_redis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedb-engine
3
- Version: 2.2.31
3
+ Version: 2.2.32
4
4
  Summary: NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd).
5
5
  Project-URL: Homepage, https://github.com/aiassistsecure/nedb
6
6
  Project-URL: Repository, https://github.com/aiassistsecure/nedb
@@ -29,8 +29,9 @@ Replay-protected · idempotent · relational · filterable · sortable · search
29
29
  One Rust core → ships to **PyPI** and **npm** from a single source.
30
30
 
31
31
  [![PyPI](https://img.shields.io/pypi/v/nedb-engine?label=PyPI&color=6366f1)](https://pypi.org/project/nedb-engine/)
32
+ [![crates.io](https://img.shields.io/crates/v/nedb-engine?label=crates.io&color=f97316)](https://crates.io/crates/nedb-engine)
32
33
  [![npm](https://img.shields.io/npm/v/nedb-engine?label=npm&color=00d4ff)](https://www.npmjs.com/package/nedb-engine)
33
- [![Tests](https://img.shields.io/badge/tests-266%20passing-34d399)](https://github.com/aiassistsecure/nedb/actions)
34
+ [![CI](https://img.shields.io/github/actions/workflow/status/Eth-Interchained/nedb/release.yml?label=CI&color=34d399)](https://github.com/Eth-Interchained/nedb/actions)
34
35
  [![nedb-engine-client PyPI](https://img.shields.io/pypi/v/nedb-engine-client?label=nedb-engine-client&color=34d399)](https://pypi.org/project/nedb-engine-client/)
35
36
  [![nedb-engine-client npm](https://img.shields.io/npm/v/nedb-engine-client?label=nedb-engine-client&color=34d399)](https://www.npmjs.com/package/nedb-engine-client)
36
37
 
@@ -8,8 +8,9 @@ Replay-protected · idempotent · relational · filterable · sortable · search
8
8
  One Rust core → ships to **PyPI** and **npm** from a single source.
9
9
 
10
10
  [![PyPI](https://img.shields.io/pypi/v/nedb-engine?label=PyPI&color=6366f1)](https://pypi.org/project/nedb-engine/)
11
+ [![crates.io](https://img.shields.io/crates/v/nedb-engine?label=crates.io&color=f97316)](https://crates.io/crates/nedb-engine)
11
12
  [![npm](https://img.shields.io/npm/v/nedb-engine?label=npm&color=00d4ff)](https://www.npmjs.com/package/nedb-engine)
12
- [![Tests](https://img.shields.io/badge/tests-266%20passing-34d399)](https://github.com/aiassistsecure/nedb/actions)
13
+ [![CI](https://img.shields.io/github/actions/workflow/status/Eth-Interchained/nedb/release.yml?label=CI&color=34d399)](https://github.com/Eth-Interchained/nedb/actions)
13
14
  [![nedb-engine-client PyPI](https://img.shields.io/pypi/v/nedb-engine-client?label=nedb-engine-client&color=34d399)](https://pypi.org/project/nedb-engine-client/)
14
15
  [![nedb-engine-client npm](https://img.shields.io/npm/v/nedb-engine-client?label=nedb-engine-client&color=34d399)](https://www.npmjs.com/package/nedb-engine-client)
15
16
 
@@ -10,7 +10,7 @@ build-backend = "hatchling.build"
10
10
 
11
11
  [project]
12
12
  name = "nedb-engine"
13
- version = "2.2.31"
13
+ version = "2.2.32"
14
14
  description = "NEDB — a versioned, self-compressing, time-traveling embedded database (replay-protected, idempotent, relational, searchable) with durable AOF persistence and a server daemon (nedbd)."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.8"
@@ -29,6 +29,7 @@ from .mongo import (
29
29
  from .autoindex import AutoIndexDB
30
30
  from .concurrent import Sequencer
31
31
  from .wrap_redis import wrap_redis, WrappedRedis
32
+ from .proof import verify_proof, fold_head
32
33
 
33
34
  try: # compiled Rust core, present in platform wheels (PyO3 via maturin)
34
35
  from . import _native # type: ignore
@@ -85,6 +86,7 @@ __all__ = [
85
86
  "MongoCompat", "MongoClient", "MongoError", "MongoUnsupportedError", "ObjectId",
86
87
  "AutoIndexDB", "Sequencer",
87
88
  "wrap_redis", "WrappedRedis",
89
+ "verify_proof", "fold_head",
88
90
  "_native", "__has_native__",
89
91
  ]
90
- __version__ = "2.2.31"
92
+ __version__ = "2.2.32"
Binary file
@@ -0,0 +1,126 @@
1
+ """
2
+ nedb.proof — Merkle inclusion proofs for the hash-chained op log.
3
+
4
+ Every NEDB write produces an Op whose ``hash`` field is the chain head after that op
5
+ (``op.hash = blake2b(prev_head.utf8 + canon(body))`` — see ``nedb.log``). The chain
6
+ head therefore commits cryptographically to the entire write history.
7
+
8
+ This module derives a *parallel*, compact Merkle commitment chain over the same
9
+ op-hash sequence so a client can verify — offline, with no server round-trip and
10
+ without re-running the engine — that a specific op-hash was at a specific seq in
11
+ the log, and that the log's head followed from chaining all subsequent op-hashes
12
+ on top of it.
13
+
14
+ The chain step used by both ``verify_proof`` (client) and the server endpoint is::
15
+
16
+ head_next = blake2b(bytes.fromhex(head_prev) || bytes.fromhex(op_hash)).hexdigest()
17
+
18
+ starting from the genesis head (the all-zero 32-byte string). Folding over the
19
+ full ordered list of op-hashes yields a deterministic head; tamper with any op
20
+ hash (or reorder ops) and the fold no longer matches. The Op's own ``hash`` field
21
+ already commits to its body (payload, seq, nonce, ts, …) under the engine's chain
22
+ formula, so this fold is a complete commitment to the write history through the
23
+ op-hashes alone — exactly the property a Merkle inclusion proof needs.
24
+
25
+ The fold's terminal value (``proof["head"]``) is a *derived* head, distinct from
26
+ ``db.head`` (the engine's UTF-8-hex chain) but a deterministic function of the
27
+ same log. The server computes the derived head over the live log when issuing
28
+ each proof, so any divergence between client fold and server fold means the
29
+ proof was tampered with in transit.
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import hashlib
34
+ from typing import Dict, List
35
+
36
+ # Genesis head matches the engine: all-zero 32-byte string in hex.
37
+ GENESIS = "0" * 64
38
+
39
+
40
+ def _blake(data: bytes) -> str:
41
+ """BLAKE2b-256 over raw bytes, returned as hex — same primitive the engine uses."""
42
+ return hashlib.blake2b(data, digest_size=32).hexdigest()
43
+
44
+
45
+ def _chain_step(head_hex: str, op_hash_hex: str) -> str:
46
+ """One Merkle-chain step on raw bytes (decoded from hex).
47
+
48
+ Mirrors the user-facing spec exactly:
49
+ new_head = blake2b(prev_head_bytes || op_hash_bytes)
50
+ """
51
+ return _blake(bytes.fromhex(head_hex) + bytes.fromhex(op_hash_hex))
52
+
53
+
54
+ def fold_head(op_hashes: List[str], start: str = GENESIS) -> str:
55
+ """Fold an ordered list of op-hashes into a Merkle-chain head.
56
+
57
+ Used by the server to produce ``proof["head"]`` for any given log state and
58
+ by tests to sanity-check that the fold is order-sensitive and deterministic.
59
+ """
60
+ head = start
61
+ for h in op_hashes:
62
+ head = _chain_step(head, h)
63
+ return head
64
+
65
+
66
+ def verify_proof(proof: Dict) -> bool:
67
+ """Verify a Merkle inclusion proof client-side — no server, no engine needed.
68
+
69
+ ``proof`` shape::
70
+
71
+ {
72
+ "hash": "<64-hex>", # the document op's content hash (op.hash)
73
+ "seq": N, # sequence number of this op in the log
74
+ "prev_head": "<64-hex>", # chain head BEFORE this op (= op.prev_hash)
75
+ "subsequent": ["<64-hex>"], # op-hashes of every op AFTER this one
76
+ "head": "<64-hex>", # claimed final (derived) chain head
77
+ }
78
+
79
+ Verification (matches the spec):
80
+ 1. ``head_at_seq = blake2b(prev_head_bytes || hash_bytes)``
81
+ 2. For each ``sub_hash`` in subsequent:
82
+ ``head = blake2b(head_bytes || sub_hash_bytes)``
83
+ 3. ``final head == proof["head"]``
84
+
85
+ Returns True iff every step is internally consistent. Tampering with any
86
+ field — flipping a byte of ``hash``, reordering ``subsequent``, swapping
87
+ ``prev_head``, etc. — will flip the fold and make this return False.
88
+ """
89
+ if not isinstance(proof, dict):
90
+ return False
91
+
92
+ # Required keys with the right shapes.
93
+ required = ("hash", "seq", "prev_head", "subsequent", "head")
94
+ if not all(k in proof for k in required):
95
+ return False
96
+ if not isinstance(proof["seq"], int) or proof["seq"] < 0:
97
+ return False
98
+ if not isinstance(proof["subsequent"], list):
99
+ return False
100
+
101
+ # Hex shape check on the 64-char fields. Anything malformed → not verified.
102
+ def _is_hex32(s: object) -> bool:
103
+ if not isinstance(s, str) or len(s) != 64:
104
+ return False
105
+ try:
106
+ bytes.fromhex(s)
107
+ except ValueError:
108
+ return False
109
+ return True
110
+
111
+ if not _is_hex32(proof["hash"]): return False
112
+ if not _is_hex32(proof["prev_head"]): return False
113
+ if not _is_hex32(proof["head"]): return False
114
+ for s in proof["subsequent"]:
115
+ if not _is_hex32(s):
116
+ return False
117
+
118
+ # 1. Re-derive the chain head AT this op's seq from prev_head + this op's hash.
119
+ head = _chain_step(proof["prev_head"], proof["hash"])
120
+
121
+ # 2. Fold each subsequent op-hash on top to advance the chain to the tail.
122
+ for sub in proof["subsequent"]:
123
+ head = _chain_step(head, sub)
124
+
125
+ # 3. The fold must equal the claimed final head bit-for-bit.
126
+ return head == proof["head"]
@@ -33,6 +33,7 @@ HTTP API (all JSON):
33
33
  POST /v1/databases/<name>/files {name, data_b64, tier?} — store a file (Cascade-compressed)
34
34
  GET /v1/databases/<name>/files/<filename>?version=N&tier=warm — retrieve a file
35
35
  GET /v1/databases/<name>/files/<filename>/root?version=N&tier=warm — Merkle root (anchorable)
36
+ POST /v1/databases/<name>/proof {hash} — Merkle inclusion proof (verifiable offline)
36
37
  POST /v1/databases/<name>/checkpoint — on-demand checkpoint
37
38
  GET /v1/databases/<name>/batch — batch writes (array of {op,coll,id,doc})
38
39
  """
@@ -368,6 +369,57 @@ def make_handler(manager: Manager, token: Optional[str]):
368
369
  ops = [o.to_dict() for o in db.log.ops[-limit:]][::-1]
369
370
  self._send(200, {"log": ops, "seq": db.seq, "head": db.head})
370
371
  return
372
+ # POST /v1/databases/<name>/proof
373
+ # Body: {"hash": "<64-hex>"} — the op.hash of a previously-written doc
374
+ # Response: a self-contained Merkle inclusion proof + a verified flag.
375
+ # The proof is verifiable client-side via nedb.verify_proof() with no
376
+ # server / engine — the server's role is only to look up the seq,
377
+ # collect the prev_head and subsequent op-hashes, and fold them into
378
+ # the same derived head the client will re-derive locally.
379
+ if method == "POST" and action == "proof":
380
+ from .proof import verify_proof as _vp, fold_head as _fold, GENESIS as _GEN
381
+ b = self._body()
382
+ want = str(b.get("hash", "")).strip().lower()
383
+ if not want or len(want) != 64:
384
+ raise HttpError(400, "hash (64-char hex) is required")
385
+ try:
386
+ bytes.fromhex(want)
387
+ except ValueError:
388
+ raise HttpError(400, "hash must be 64-char hexadecimal")
389
+ # Find the Op with this exact chain-hash.
390
+ ops = db.log.ops
391
+ target = None
392
+ for o in ops:
393
+ if o.hash == want:
394
+ target = o
395
+ break
396
+ if target is None:
397
+ raise HttpError(404, f"no op with hash={want[:16]}…")
398
+ # The proof scheme is a Merkle fold over op-hashes — a
399
+ # PARALLEL commitment to the engine's per-op chain. Both
400
+ # are deterministic functions of the same log. Replay the
401
+ # fold from GENESIS to determine the fold-value just before
402
+ # this op (prev_head), then collect every subsequent op-hash
403
+ # so the client can fold forward to the same derived head.
404
+ op_hashes = [o.hash for o in ops]
405
+ prev_head = _fold(op_hashes[: target.seq], start=_GEN)
406
+ subsequent = op_hashes[target.seq + 1:]
407
+ derived_head = _fold(op_hashes, start=_GEN)
408
+ proof = {
409
+ "hash": target.hash,
410
+ "seq": target.seq,
411
+ "prev_head": prev_head,
412
+ "subsequent": subsequent,
413
+ "head": derived_head,
414
+ }
415
+ # Self-check: confirm the proof verifies before returning it,
416
+ # so a 200 from this endpoint is a hard guarantee of soundness.
417
+ proof["verified"] = _vp(proof)
418
+ # Helpful extras for clients that want to anchor to db.head too.
419
+ proof["engine_head"] = db.head
420
+ proof["engine_seq"] = db.seq
421
+ self._send(200, proof)
422
+ return
371
423
  # MongoDB-compatible query endpoint
372
424
  # POST /v1/databases/<name>/mongo
373
425
  # Body: {collection, op, ...op-specific args}
@@ -3,8 +3,8 @@
3
3
  **NEDB v2 — content-addressed DAG storage engine with NQL and HTTP server**
4
4
 
5
5
  [![crates.io](https://img.shields.io/crates/v/nedb-engine?color=f97316)](https://crates.io/crates/nedb-engine)
6
- [![License: BUSL-1.1](https://img.shields.io/badge/license-BUSL--1.1-blue)](https://github.com/Eth-Interchained/nedb/blob/master/LICENSE)
7
- [![GitHub](https://img.shields.io/badge/github-Eth--Interchained%2Fnedb-8da0cb)](https://github.com/Eth-Interchained/nedb)
6
+ [![License: BUSL-1.1](https://img.shields.io/badge/license-BUSL--1.1-blue)](https://github.com/aiassistsecure/nedb/blob/master/LICENSE)
7
+ [![GitHub](https://img.shields.io/badge/github-aiassistsecure%2Fnedb-8da0cb)](https://github.com/aiassistsecure/nedb)
8
8
 
9
9
  This crate ships the **`nedbd`** binary — the NEDB v2 DAG HTTP server. Install it, point it at a data directory, and any language can speak to it over HTTP/JSON.
10
10
 
@@ -172,7 +172,7 @@ nedbd --doctor
172
172
 
173
173
  ## License
174
174
 
175
- [BUSL-1.1](https://github.com/Eth-Interchained/nedb/blob/master/LICENSE) — Business Source License. Free for development and evaluation; production use requires a commercial licence from INTERCHAINED, LLC.
175
+ [BUSL-1.1](https://github.com/aiassistsecure/nedb/blob/master/LICENSE) — Business Source License. Free for development and evaluation; production use requires a commercial licence from INTERCHAINED, LLC.
176
176
 
177
177
  ---
178
178
 
Binary file
Binary file
File without changes
File without changes