Skip to content

Commit

Permalink
Add CBOR encoding for HashTree.
Browse files Browse the repository at this point in the history
  • Loading branch information
q-uint committed Apr 12, 2024
1 parent f2e03bb commit bf927a1
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 39 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ let value = encode(#TextString("Hello world!"));

[Read more...](./src/cbor/README.md)

### Certified Variables

Canister smart contracts can declare variables as certified. Whenever set, these
variables will automatically get a Merkle tree certificate, signed by the
Internet Computer blockchain. This allows anyone to verify the authenticity of
this type of data using the Internet Computer's public key.

[Read more...](./src/certified/README.md)

### Crypto

The Crypto library offers a collection of cryptographic functions and utilities,
Expand Down
29 changes: 20 additions & 9 deletions src/cbor/CBOR.mo
Original file line number Diff line number Diff line change
Expand Up @@ -461,20 +461,31 @@ module {
case (#ByteString(s)) {
let l = s.size();
if (l <= 23) {
buffer.add(nat5b3(2, Nat8.fromNat(l)));
buffer.add(nat5b3(2, Nat8.fromIntWrap(l)));
} else {
if (l < 0xff) {
buffer.add(nat5b3(6, 24));
buffer.add(Nat8.fromNat(l));
buffer.add(nat5b3(2, 24));
buffer.add(Nat8.fromIntWrap(l));
} else if (l < 0xffff) {
buffer.add(nat5b3(6, 25))
// TODO
buffer.add(nat5b3(2, 25));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 8)));
buffer.add(Nat8.fromIntWrap(l));
} else if (l < 0xffff) {
buffer.add(nat5b3(6, 26))
// TODO
buffer.add(nat5b3(2, 26));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 24)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 16)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 8)));
buffer.add(Nat8.fromIntWrap(l));
} else {
buffer.add(nat5b3(6, 27))
// TODO
buffer.add(nat5b3(2, 27));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 56)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 48)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 40)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 32)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 24)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 16)));
buffer.add(Nat8.fromIntWrap(shiftLeft(l, 8)));
buffer.add(Nat8.fromIntWrap(l));
};
};
for (c in s.vals()) buffer.add(c);
Expand Down
94 changes: 69 additions & 25 deletions src/certified/HashTree.mo
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Blob "mo:base/Blob";
import Debug "mo:base/Debug";
import Array "mo:base/Array";

import CBOR "../cbor/CBOR";
import SHA256 "../crypto/SHA256";

module {
Expand All @@ -18,38 +20,80 @@ module {

public func reconstruct(t : HashTree) : Hash {
switch (t) {
case (#empty) { hashEmpty() };
case (#fork(l, r)) { hashFork(reconstruct(l), reconstruct(r)) };
case (#labeled(k, t)) { hashLabeled(k, reconstruct(t)) };
case (#leaf(v)) { hashLeaf(v) };
case (#pruned(prunedHash)) { prunedHash };
case (#empty) { hash.empty() };
case (#fork(l, r)) { hash.fork(reconstruct(l), reconstruct(r)) };
case (#labeled(k, t)) { hash.labeled(k, reconstruct(t)) };
case (#leaf(v)) { hash.leaf(v) };
case (#pruned(h)) { h };
};
};

private func hashEmpty() : Hash {
SHA256.sum(Blob.toArray("\11ic-hashtree-empty"));
};
private module hash = {
public func empty() : Hash {
SHA256.sum(Blob.toArray("\11ic-hashtree-empty"));
};

public func fork(l : Hash, r : Hash) : Hash {
let digest = SHA256.SHA256();
digest.write(Blob.toArray("\10ic-hashtree-fork"));
digest.write(l);
digest.write(r);
digest.checkSum();
};

public func labeled(k : Key, h : Hash) : Hash {
let digest = SHA256.SHA256();
digest.write(Blob.toArray("\13ic-hashtree-labeled"));
digest.write(k);
digest.write(h);
digest.checkSum();
};

private func hashFork(l : Hash, r : Hash) : Hash {
let digest = SHA256.SHA256();
digest.write(Blob.toArray("\10ic-hashtree-fork"));
digest.write(l);
digest.write(r);
digest.checkSum();
public func leaf(v : Value) : Hash {
let digest = SHA256.SHA256();
digest.write(Blob.toArray("\10ic-hashtree-leaf"));
digest.write(v);
digest.checkSum();
};
};

private func hashLabeled(k : Key, h : Hash) : Hash {
let digest = SHA256.SHA256();
digest.write(Blob.toArray("\13ic-hashtree-labeled"));
digest.write(k);
digest.write(h);
digest.checkSum();
public func encodeCBOR(t : HashTree) : Hash {
Blob.toArray(CBOR.encode(cbor.tree(t)));
};

private func hashLeaf(v : Value) : Hash {
let digest = SHA256.SHA256();
digest.write(Blob.toArray("\10ic-hashtree-leaf"));
digest.write(v);
digest.checkSum();
private module cbor = {
public func tree(t : HashTree) : CBOR.Value = switch (t) {
case (#empty) { empty() };
case (#fork(l, r)) { fork(l, r) };
case (#labeled(k, t)) { labeled(k, t) };
case (#leaf(v)) { leaf(v) };
case (#pruned(h)) { pruned(h) };
};

public func empty() : CBOR.Value = #Array([
#UnsignedInteger(0),
]);

public func fork(l : HashTree, r : HashTree) : CBOR.Value = #Array([
#UnsignedInteger(1),
tree(l),
tree(r),
]);

public func labeled(k : Key, t : HashTree) : CBOR.Value = #Array([
#UnsignedInteger(2),
#ByteString(Blob.fromArray(k)),
tree(t),
]);

public func leaf(v : Value) : CBOR.Value = #Array([
#UnsignedInteger(3),
#ByteString(Blob.fromArray(v)),
]);

public func pruned(h : Hash) : CBOR.Value = #Array([
#UnsignedInteger(4),
#ByteString(Blob.fromArray(h)),
]);
};
};
19 changes: 19 additions & 0 deletions src/certified/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Cerfified Variables

The Internet Computer supports a scheme where a canister can sign a payload by
declaring a special "certified variable".

[Read more...](https://internetcomputer.org/how-it-works/response-certification/)

```motoko
import HashTree "mo:core/certified/HashTree";
```

## Interface

```motoko
module {
encodeCBOR : HashTree -> Hash;
reconstruct : HashTree -> Hash;
};
```
17 changes: 12 additions & 5 deletions test/certified/HashTree.mo
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { decode } = "mo:core/encoding/Hex";
import { reconstruct } "mo:core/certified/HashTree";
import { reconstruct; encodeCBOR } "mo:core/certified/HashTree";
import { encodeUtf8 } = "mo:base/Text";
import Debug "mo:base/Debug";

func b(t : Text) : [Nat8] = Blob.toArray(encodeUtf8(t));
func xb(t : Text) : [Nat8] = switch (decode(t)) {
Expand All @@ -12,7 +11,7 @@ func xb(t : Text) : [Nat8] = switch (decode(t)) {
};
};

assert(Hex.encode(reconstruct(#fork(
let prunedTree = #fork(
#fork(
#labeled(b("a"), #fork(
#pruned(xb("1b4feff9bef8131788b0c9dc6dbad6e81e524249c879e9f10f71ce3749f5a638")),
Expand All @@ -24,7 +23,9 @@ assert(Hex.encode(reconstruct(#fork(
#pruned(xb("ec8324b8a1f1ac16bd2e806edba78006479c9877fed4eb464a25485465af601d")),
#labeled(b("d"), #leaf(b("morning"))),
),
))) == Hex.encode(reconstruct(#fork(
);

let tree = #fork(
#fork(
#labeled(b("a"), #fork(
#fork(
Expand All @@ -39,4 +40,10 @@ assert(Hex.encode(reconstruct(#fork(
#labeled(b("c"), #empty),
#labeled(b("d"), #leaf(b("morning"))),
),
))));
);

assert(Hex.encode(reconstruct(prunedTree)) == "eb5c5b2195e62d996b84c9bcc8259d19a83786a2f59e0878cec84c811f669aa0");
assert(Hex.encode(reconstruct(prunedTree)) == Hex.encode(reconstruct(tree)));

assert(Hex.encode(encodeCBOR(tree)) == "8301830183024161830183018302417882034568656c6c6f810083024179820345776f726c6483024162820344676f6f648301830241638100830241648203476d6f726e696e67");
assert(Hex.encode(encodeCBOR(prunedTree)) == "83018301830241618301820458201b4feff9bef8131788b0c9dc6dbad6e81e524249c879e9f10f71ce3749f5a63883024179820345776f726c6483024162820458207b32ac0c6ba8ce35ac82c255fc7906f7fc130dab2a090f80fe12f9c2cae83ba6830182045820ec8324b8a1f1ac16bd2e806edba78006479c9877fed4eb464a25485465af601d830241648203476d6f726e696e67");

0 comments on commit bf927a1

Please sign in to comment.