Skip to content

Commit 9ddbf3b

Browse files
committed
Initial commit
0 parents  commit 9ddbf3b

File tree

13 files changed

+2609
-0
lines changed

13 files changed

+2609
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/target
2+
/Cargo.lock
3+
*.dot
4+
*.png
5+
.idea
6+
*.sdb

Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
workspace = { members = ["example"] }
2+
3+
[package]
4+
name = "spacedb"
5+
version = "0.1.0"
6+
edition = "2021"
7+
description = "A cryptographically verifiable data store and universal accumulator for the Spaces protocol."
8+
repository = "https://github.com/spacesprotocol/spacedb"
9+
license = "Apache-2.0"
10+
11+
[dependencies]
12+
libc = { version = "0.2.150", optional = true }
13+
bincode = { version = "2.0.0-rc.3", default-features = false, features = ["alloc"] }
14+
hex = { version = "0.4.3", optional = true }
15+
16+
[dependencies.sha2]
17+
git = "https://github.com/risc0/RustCrypto-hashes"
18+
tag = "sha2-v0.10.6-risczero.0"
19+
default-features = false
20+
21+
[features]
22+
default = ["std"]
23+
std = ["libc", "hex", "bincode/derive", "bincode/std"]

README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# SpaceDB
2+
3+
<small>Note: this project is still under active development and should be considered experimental.</small>
4+
5+
SpaceDB is a cryptographically verifiable data store and universal accumulator for the [Spaces protocol](https://spacesprotocol.com). It's a Merkle-ized binary trie described in the [Merklix](https://blog.vermorel.com/pdf/merklix-tree-for-bitcoin-2018-07.pdf) paper and explained in detail [here](https://spacesprotocol.org/#binary-trie).
6+
7+
8+
## Features
9+
10+
- Fast, portable, single-file database.
11+
- MVCC-based concurrency control with multi-reader/single-writer lock-free access.
12+
- Provides compact proofs of membership/non-membership for batches of elements through subtrees.
13+
- Subtrees act as cryptographic accumulators and can be updated independently.
14+
- `no_std` support, particularly for use within RISC0 zkVM and leverages SHA256 acceleration.
15+
- Accumulator keeps a constant size state of a single 32-byte tree root.
16+
17+
18+
19+
## Usage
20+
21+
```rust
22+
use spacedb::db::Database;
23+
24+
25+
let db = Database::open("example.sdb")?;
26+
27+
// Insert some data
28+
let mut tx = db.begin_write()?;
29+
for i in 0..100 {
30+
let key = format!("key{}", i);
31+
let value = format!("value{}", i);
32+
tx.insert(db.hash(key.as_bytes()), value.into_bytes())?;
33+
}
34+
tx.commit()?;
35+
36+
let mut snapshot = db.begin_read()?;
37+
println!("Tree root: {}", hex::encode(snapshot.root()?));
38+
39+
// Prove a subset of the keys
40+
let keys_to_prove: Vec<_> = (0..10)
41+
.map(|i| format!("key{}", i))
42+
// prove exclusion of some other keys
43+
.chain((0..5).map(|i| format!("other{}", i)))
44+
.map(|key| db.hash(key.as_bytes()))
45+
.collect();
46+
47+
// Reveal relevant nodes needed to prove the specified set of keys
48+
let mut subtree = snapshot.prove_all(&keys_to_prove)?;
49+
50+
// Will have the exact same root as the snapshot
51+
println!("Subtree root: {}", hex::encode(subtree.root().unwrap()));
52+
53+
// Inclusion and exclusion proofs
54+
assert!(subtree.contains(&db.hash("key0".as_bytes())).unwrap());
55+
assert!(!subtree.contains(&db.hash("other0".as_bytes())).unwrap());
56+
57+
// Proving exclusion of "other100" fails since we didn't reveal
58+
// relevant branches needed to traverse its path in this subtree
59+
assert!(subtree.contains(&db.hash("other100".as_bytes())).is_err());
60+
61+
```
62+
63+
64+
65+
## Subtrees
66+
67+
Subtrees can function as cryptographic accumulators, allowing clients to verify and update their state without keeping a database.
68+
69+
```rust
70+
71+
// Client maintains a 32-byte tree root
72+
let mut accumulator_root = snapshot.root()?;
73+
assert_eq!(accumulator_root, subtree.root().unwrap(), "Roots must match");
74+
75+
// Update leaves
76+
for (key, value) in subtree.iter_mut() {
77+
*value = "new value".to_string().into_bytes();
78+
}
79+
80+
// Inserting a non-existent key (must be provably absent)
81+
let key = subtree.hash("other0".as_bytes());
82+
subtree.insert(key, "new value".into_bytes()).unwrap();
83+
84+
// Updating the accumulator root
85+
accumulator_root = subtree.root().unwrap();
86+
87+
```
88+
89+
## Using in RISC0 zkVM
90+
91+
Subtrees work in `no_std` environments utilizing the SHA256 accelerator when running inside the RISC0 zkVM.
92+
93+
```toml
94+
[dependencies]
95+
spacedb = { version = "0.1", default-features = false }
96+
```
97+
98+
99+
100+
101+
## Key Iteration
102+
103+
Iterate over all keys in a given snapshot:
104+
105+
```rust
106+
let db = Database::open("my.sdb")?;
107+
let snapshot = db.begin_read()?;
108+
109+
for (key, value) in snapshot.iter().filter_map(Result::ok) {
110+
// do something ...
111+
}
112+
113+
```
114+
115+
116+
117+
## Snapshot iteration
118+
119+
Iterate over all snapshots:
120+
121+
```rust
122+
let db = Database::open("my.sdb")?;
123+
124+
for snapshot in db.iter().filter_map(Result::ok) {
125+
let root = snapshot.root()?;
126+
println!("Snapshot Root: {}", hex::encode(root));
127+
}
128+
```
129+
130+
## Prior Art
131+
132+
Merkle-ized tries, including variations like Patricia tries and Merkle prefix trees, are foundational structures that have been used in numerous projects and cryptocurrencies. Some other libraries that implement some form of Merkle-ized binary tries include
133+
[liburkel](https://github.com/chjj/liburkel) which this library initially drew some inspiration from — although SpaceDB is generally around ~20% faster, and [multiproof,](https://github.com/gballet/multiproof-rs/tree/master) but they either lack memory safety, core features such as subtrees/accumulators needed for Spaces protocol or are unmaintained. Other popular cryptographically verifiable data stores include [Trillian](https://github.com/google/trillian) used for [Certificate Transparency](https://www.certificate-transparency.org/)
134+
135+
136+
## License
137+
138+
This project is licensed under the [Apache 2.0](LICENSE).

example/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "example"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
spacedb = {version = "*", path = ".." }
10+
hex = "0.4.3"
11+

example/src/main.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use spacedb::db::Database;
2+
3+
fn main() -> Result<(), std::io::Error> {
4+
let db = Database::memory()?;
5+
6+
// Insert some data
7+
let mut tx = db.begin_write()?;
8+
for i in 0..100 {
9+
let key = format!("key{}", i);
10+
let value = format!("value{}", i);
11+
tx.insert(db.hash(key.as_bytes()), value.into_bytes())?;
12+
}
13+
tx.commit()?;
14+
15+
// Get the committed snapshot
16+
let mut snapshot = db.begin_read()?;
17+
println!("Tree root: {}", hex::encode(snapshot.root()?));
18+
19+
// Prove a subset of the keys
20+
let keys_to_prove: Vec<_> = (0..10)
21+
.map(|i| format!("key{}", i))
22+
// prove exclusion of some other keys
23+
.chain((0..5).map(|i| format!("other{}", i)))
24+
.map(|key| db.hash(key.as_bytes()))
25+
.collect();
26+
27+
// reveal the relevant nodes needed to prove the specified set of keys
28+
let subtree = snapshot.prove_all(&keys_to_prove)?;
29+
30+
// Will have the exact same root as the snapshot
31+
println!("Subtree root: {}", hex::encode(subtree.root().unwrap()));
32+
33+
// Prove inclusion
34+
assert!(subtree.contains(&db.hash("key0".as_bytes())).unwrap());
35+
36+
// Prove exclusion
37+
assert!(!subtree.contains(&db.hash("other0".as_bytes())).unwrap());
38+
39+
// We don't have enough data to prove key "other100" is not in the subtree
40+
// as the relevant branches needed to prove it are not included
41+
assert!(subtree.contains(&db.hash("other100".as_bytes())).is_err());
42+
Ok(())
43+
}

0 commit comments

Comments
 (0)