Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ jobs:
with:
node-version: 20.13.1

- name: 🧰 Setup Aiken
uses: aiken-lang/setup-aiken@v1
with:
version: v1.1.17

- name: 🌍 Install dependencies
working-directory: off-chain
run: yarn
Expand All @@ -52,10 +57,10 @@ jobs:
- name: 🧰 Setup Pages
uses: actions/configure-pages@v5

- name: 🧰 Install Aiken
- name: 🧰 Setup Aiken
uses: aiken-lang/setup-aiken@v1
with:
version: v1.1.4
version: v1.1.17

- name: 📝 Run fmt
working-directory: on-chain
Expand Down
8 changes: 8 additions & 0 deletions off-chain/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

- Allow [`childAt`](https://github.com/aiken-lang/merkle-patricia-forestry/tree/main/off-chain#triechildatpath-string-promisetrieundefined) to return with intermediate sub-tree when passing an incomplete path.

- Adjust `trie.prove` to now accept an optional (default=`false`) flag: `allowMissing`. When set, it becomes possible to build partial proofs for elements that
are not in the trie. The proof can in particular be verified in exclusion to prove that the given element isn't in the tree (i.e. non-membership).

- New utility functions for proofs:
- [`Proof.fromJSON(key, value, steps): Proof`](https://github.com/aiken-lang/merkle-patricia-forestry/tree/main/off-chain#prooffromjson-key-value-steps-proof) to recover a proof from an already serialized JSON value.
- [`Proof.setValue(value)`](https://github.com/aiken-lang/merkle-patricia-forestry/tree/main/off-chain#proofsetvaluevalue) to quickly change the value associated with a proof (without having to re-calculate a whole proof).
- [`Proof.toUPLC()`](https://github.com/aiken-lang/merkle-patricia-forestry/tree/main/off-chain#prooftouplc) provides a proof in a textual UPLC format; handy to use with `aiken uplc eval`.

## v1.2.0 - 2024-10-17

### Added
Expand Down
53 changes: 45 additions & 8 deletions off-chain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ const apple = await trie.childAt(

### Proving

#### `trie.prove(key: string|Buffer): Promise<Proof>`
#### `trie.prove(key: string|Buffer, allowMissing?: bool): Promise<Proof>`

Let's get to the exciting part! The whole point of building a Merkle Patricia Forestry is to provide succinct proofs for items. A proof is portable and bound to both:

Expand All @@ -225,6 +225,14 @@ const proofTangerine = await trie.prove('tangerine');
// Proof {}
```

Note that it is possible to create partial proofs (or imply, proof of exclusion) for elements that are not in the trie. This is useful to prove non-membership: verifying the proof in exclusion would yield the current trie root.

```js
// 'melon' isn't in the Trie, but allowMissing is set `true`
const proofMelon = await trie.prove('melon', true);
// Proof {}
```

#### `proof.verify(includingItem?: bool = true): Buffer`

A proof can be _verified_ by calling `.verify` on it. The verification is fully synchronous and yields a hash as a byte `Buffer`. If that hash matches the trie root hash, the proof is valid.
Expand Down Expand Up @@ -261,6 +269,10 @@ proofBanana.verify(false).equals(previousHash);
> }
> ```

#### `proof.setValue(value)`

Once a proof has been generated for a given key/value element; it is possible to quickly change the value associated with that proof using `.setValue` so as the key -- and the rest of the trie; stay the same.

#### `proof.toJSON(): object`

Proofs are opaque but can be serialised into various formats, such as JSON. The proof format is explained in greater detail in [the wiki](https://github.com/aiken-lang/merkle-patricia-forestry/wiki/Proof-format).
Expand Down Expand Up @@ -330,11 +342,36 @@ For convenience, you can also generate Aiken code that works with the on-chain p

```js
proofTangerine.toAiken();
// [
// Branch { skip: 0, neighbors: #"17a27bc4ce61078d26372800d331d6b8c4b00255080be66977c78b1554aabf8985c09af929492a871e4fae32d9d5c36e352471c
// d659bcdb61de08f1722acc3b10eb923b0cbd24df54401d998531feead35a47a99f4deed205de4af81120f976100000000000000000000000000000000000000000000000
// 00000000000000000" },
// Leaf { skip: 0, key: #"9702e39845bfd6e0d0a5b6cb4a3a1c25262528c11bcff857867a50a0670e3a28", value: #"b5898c51c32083e91b8c18c735d0ba74e08
// f964a20b1639c189d1e8704b78a09" },
// ]
```

```aiken
[
Branch {
skip: 0,
neighbors: #"17a27bc4ce61078d26372800d331d6b8c4b00255080be66977c78b1554aabf8985c09af929492a871e4fae32d9d5c36e352471cd659bcdb61de08f1722acc3b10eb923b0cbd24df54401d998531feead35a47a99f4deed205de4af81120f97610000000000000000000000000000000000000000000000000000000000000000",
},
Leaf {
skip: 0,
key: #"9702e39845bfd6e0d0a5b6cb4a3a1c25262528c11bcff857867a50a0670e3a28",
value: #"b5898c51c32083e91b8c18c735d0ba74e08f964a20b1639c189d1e8704b78a09",
},
]
```

#### `proof.toUPLC(): String`

Finally, one can also convert proofs in a textual UPLC format which as currently expected by some tools such as `aiken uplc eval`.

```js
proofTangerine.toAiken();
```

```uplc
(con data
(List
[ Constr 0 [I 0, B #"17a27bc4ce61078d26372800d331d6b8c4b00255080be66977c78b1554aabf8985c09af929492a871e4fae32d9d5c36e352471cd659bcdb61de08f1722acc3b10eb923b0cbd24df54401d998531feead35a47a99f4deed205de4af81120f97610000000000000000000000000000000000000000000000000000000000000000"]
, Constr 2 [I 0, B #"9702e39845bfd6e0d0a5b6cb4a3a1c25262528c11bcff857867a50a0670e3a28", B #"b5898c51c32083e91b8c18c735d0ba74e08f964a20b1639c189d1e8704b78a09"]
]
)
)
```
18 changes: 17 additions & 1 deletion off-chain/lib/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export class Store {
}

async get(key, deserialise) {
return deserialise(key, await this.#db.get((key ?? NULL_HASH).toString('hex')), this);
const dbKey = (key ?? NULL_HASH).toString('hex');
const dbValue = await this.#db.get(dbKey);
return deserialise(key, applyBatch(dbKey, dbValue, this.#batch), this);
}

async put(key, value) {
Expand Down Expand Up @@ -97,3 +99,17 @@ function inMemoryMap() {
},
}
}

function applyBatch(key, value, batch = []) {
return batch.reduce((newValue, op) => {
if (op.type === 'del' && op.key === key) {
return undefined;
}

if (op.type === 'put' && op.key === key) {
return op.value;
}

return newValue;
}, value);
}
Loading
Loading