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.
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/PKG-INFO +3 -2
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/README.md +2 -1
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/pyproject.toml +1 -1
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/__init__.py +3 -1
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/_native.abi3.so +0 -0
- nedb_engine-2.2.32/python/nedb/nedbd-v2 +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/nedbd-v2-darwin-arm64 +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/nedbd-v2-darwin-x64 +0 -0
- nedb_engine-2.2.32/python/nedb/nedbd-v2.exe +0 -0
- nedb_engine-2.2.32/python/nedb/proof.py +126 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/server.py +52 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/rust/nedb-v2/README.md +3 -3
- nedb_engine-2.2.31/python/nedb/nedbd-v2 +0 -0
- nedb_engine-2.2.31/python/nedb/nedbd-v2.exe +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/.gitignore +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/LICENSE +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/client/node/README.md +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/client/python/README.md +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/autoindex.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/backends/__init__.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/backends/redis_backend.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/cascade.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/concurrent.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/crypto.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/engine.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/index.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/log.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/merkle.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/mongo.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/query.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/redis_compat.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/relations.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/resp2.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/snapshot.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/sql.py +0 -0
- {nedb_engine-2.2.31 → nedb_engine-2.2.32}/python/nedb/store.py +0 -0
- {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.
|
|
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
|
[](https://pypi.org/project/nedb-engine/)
|
|
32
|
+
[](https://crates.io/crates/nedb-engine)
|
|
32
33
|
[](https://www.npmjs.com/package/nedb-engine)
|
|
33
|
-
[](https://github.com/Eth-Interchained/nedb/actions)
|
|
34
35
|
[](https://pypi.org/project/nedb-engine-client/)
|
|
35
36
|
[](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
|
[](https://pypi.org/project/nedb-engine/)
|
|
11
|
+
[](https://crates.io/crates/nedb-engine)
|
|
11
12
|
[](https://www.npmjs.com/package/nedb-engine)
|
|
12
|
-
[](https://github.com/Eth-Interchained/nedb/actions)
|
|
13
14
|
[](https://pypi.org/project/nedb-engine-client/)
|
|
14
15
|
[](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.
|
|
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.
|
|
92
|
+
__version__ = "2.2.32"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
[](https://crates.io/crates/nedb-engine)
|
|
6
|
-
[](https://github.com/
|
|
7
|
-
[](https://github.com/aiassistsecure/nedb/blob/master/LICENSE)
|
|
7
|
+
[](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/
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|