diff --git a/README.md b/README.md index e608998..4138302 100644 --- a/README.md +++ b/README.md @@ -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, diff --git a/src/cbor/CBOR.mo b/src/cbor/CBOR.mo index 50237ec..8a80ef9 100755 --- a/src/cbor/CBOR.mo +++ b/src/cbor/CBOR.mo @@ -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); diff --git a/src/certified/HashTree.mo b/src/certified/HashTree.mo index a7b5da3..ddea4d1 100644 --- a/src/certified/HashTree.mo +++ b/src/certified/HashTree.mo @@ -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 { @@ -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)), + ]); }; }; diff --git a/src/certified/README.md b/src/certified/README.md new file mode 100644 index 0000000..ec99e5a --- /dev/null +++ b/src/certified/README.md @@ -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; +}; +``` diff --git a/test/certified/HashTree.mo b/test/certified/HashTree.mo index aa8b44f..bbdb840 100644 --- a/test/certified/HashTree.mo +++ b/test/certified/HashTree.mo @@ -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)) { @@ -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")), @@ -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( @@ -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");