diff --git a/Cargo.lock b/Cargo.lock index a6146a27..5fda417a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,17 @@ dependencies = [ "term", ] +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "atty" version = "0.2.14" @@ -55,6 +66,41 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "beef" version = "0.5.2" @@ -115,6 +161,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -124,6 +179,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "digest 0.9.0", + "ff 0.12.1", + "group 0.12.1", + "pairing", + "rand_core", + "subtle", +] + [[package]] name = "bumpalo" version = "3.13.0" @@ -136,6 +205,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "candid" version = "0.8.4" @@ -161,7 +236,7 @@ dependencies = [ "pretty", "serde", "serde_bytes", - "sha2", + "sha2 0.10.6", "thiserror", ] @@ -253,6 +328,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + [[package]] name = "cpufeatures" version = "0.2.7" @@ -356,6 +437,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -372,20 +465,42 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", + "const-oid", "crypto-common", + "subtle", ] [[package]] @@ -409,12 +524,46 @@ dependencies = [ "winapi", ] +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff 0.13.0", + "generic-array", + "group 0.13.0", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "ena" version = "0.14.2" @@ -424,6 +573,15 @@ dependencies = [ "log", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + [[package]] name = "errno" version = "0.3.1" @@ -454,6 +612,26 @@ dependencies = [ "instant", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -466,6 +644,76 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -474,6 +722,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -487,6 +736,47 @@ dependencies = [ "wasi", ] +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" @@ -529,6 +819,124 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" +dependencies = [ + "http", + "hyper", + "rustls 0.21.2", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "ic-agent" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d89847df1b6514cbfc7bd10d0d6d191ed136ddfe592c64b5204b3fe4d07f475" +dependencies = [ + "async-trait", + "backoff", + "base32", + "byteorder", + "candid", + "futures-util", + "hex", + "http", + "http-body", + "ic-certification", + "ic-verify-bls-signature", + "k256", + "leb128", + "mime", + "pem", + "pkcs8", + "rand", + "reqwest", + "ring", + "rustls 0.20.8", + "sec1", + "serde", + "serde_bytes", + "serde_cbor", + "serde_repr", + "sha2 0.10.6", + "simple_asn1", + "thiserror", + "tokio", + "url", +] + [[package]] name = "ic-cdk" version = "0.6.10" @@ -557,25 +965,61 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ic-certification" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce514ae665701b9d85d5fbba87d177befef83a808b92adf53e2496376303a910" +dependencies = [ + "hex", + "serde", + "serde_bytes", + "sha2 0.10.6", +] + [[package]] name = "ic-stable-structures" version = "0.5.4" dependencies = [ "criterion", + "ic-agent", "ic-cdk", "ic-cdk-macros", "lazy_static", "maplit", "proptest", + "sha2 0.10.6", "tempfile", ] +[[package]] +name = "ic-verify-bls-signature" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583b1c03380cf86059160cc6c91dcbf56c7b5f141bf3a4f06bc79762d775fac4" +dependencies = [ + "bls12_381", + "lazy_static", + "pairing", + "sha2 0.9.9", +] + [[package]] name = "ic0" version = "0.18.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187fa0cecf46628330b7a390a1a65fb0637ea00d3a1121aa847ecbebc0f3ff79" +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -606,6 +1050,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" + [[package]] name = "is-terminal" version = "0.4.7" @@ -642,6 +1092,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.6", + "signature", +] + [[package]] name = "lalrpop" version = "0.19.12" @@ -763,6 +1227,23 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -844,6 +1325,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "os_str_bytes" version = "6.5.0" @@ -851,7 +1338,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] -name = "parking_lot" +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + +[[package]] +name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" @@ -879,6 +1375,31 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +[[package]] +name = "pem" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13fe415cdf3c8e44518e18a7c95a13431d9bdf6d15367d82b23c377fdd441a" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + [[package]] name = "petgraph" version = "0.6.3" @@ -898,6 +1419,28 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "plotters" version = "0.3.4" @@ -1115,6 +1658,72 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.2", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustix" version = "0.37.19" @@ -1129,6 +1738,49 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e32ca28af694bc1bbf399c33a516dbdf1c90090b8ab23c2bc24f834aa2247f5f" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -1168,6 +1820,30 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "serde" version = "1.0.163" @@ -1186,6 +1862,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.163" @@ -1208,6 +1894,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "serde_tokenstream" version = "0.1.7" @@ -1219,6 +1916,31 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.6" @@ -1227,7 +1949,29 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", ] [[package]] @@ -1236,12 +1980,47 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "string_cache" version = "0.8.7" @@ -1255,6 +2034,12 @@ dependencies = [ "precomputed-hash", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1336,6 +2121,33 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "time" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -1361,6 +2173,61 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.2", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml_datetime" version = "0.6.2" @@ -1378,6 +2245,38 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typed-arena" version = "2.0.2" @@ -1396,12 +2295,27 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + [[package]] name = "unicode-ident" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.10" @@ -1414,6 +2328,23 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "version_check" version = "0.9.4" @@ -1439,6 +2370,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1470,6 +2410,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.86" @@ -1499,6 +2451,19 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +[[package]] +name = "wasm-streams" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.63" @@ -1509,6 +2474,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1680,3 +2664,18 @@ checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 0f7bd949..66239bef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,12 @@ keywords = ["internet-computer", "dfinity", "stable-structures"] include = ["src", "Cargo.toml", "LICENSE", "README.md"] repository = "https://github.com/dfinity/stable-structures" +[dependencies] +sha2 = "0.10" + [dev-dependencies] criterion = "0.4.0" +ic-agent = "0.24" ic-cdk = "0.6.8" ic-cdk-macros = "0.6.8" lazy_static = "1.4.0" diff --git a/src/certification.rs b/src/certification.rs new file mode 100644 index 00000000..db3fc176 --- /dev/null +++ b/src/certification.rs @@ -0,0 +1,9 @@ +pub trait WitnessBuilder { + type Tree; + + fn empty() -> Self::Tree; + fn fork(l: Self::Tree, r: Self::Tree) -> Self::Tree; + fn node(label: &[u8], child: Self::Tree) -> Self::Tree; + fn leaf(bytes: &[u8]) -> Self::Tree; + fn pruned(hash: [u8; 32]) -> Self::Tree; +} diff --git a/src/certified_seq.rs b/src/certified_seq.rs new file mode 100644 index 00000000..e7c661b8 --- /dev/null +++ b/src/certified_seq.rs @@ -0,0 +1,315 @@ +#![allow(dead_code)] +//! This module implements a certified sequence data structure +//! compatible with the [IC certification scheme](https://internetcomputer.org/docs/current/references/ic-interface-spec#certification). +//! +//! The implementation is based on Dat's Hypercore [Flat Trees](https://dat-ecosystem-archive.github.io/book/ch01-01-flat-tree.HTML) +//! but has important differences in how it handles independent forests. +//! +//! A certified sequence is a flat in-order tree stored as a stable array of hashes. +//! Items at even indices are hashes of the values; items at odd indices are internal nodes. +//! The number of elements in the tree is always odd and equal to (2 * n - 1) where n is leaves count. +//! +//! For example, we represent a tree with 7 leaves as an array with 13 nodes. +//! +//! ```text +//! H | 0 1 2 3 4 5 6 7 8 9 a b c +//! ______x_____ +//! / \ +//! x \ +//! / \ x +//! / \ _/ \ +//! x x x ` +//! / \ / \ / \ | +//! T | 0 _ 1 _ 3 _ 4 _ 5 _ 6 _ 7 +//! ``` + +#[cfg(test)] +mod tests; +#[cfg(test)] +mod tree_tests; + +use crate::base_vec::{BaseVec, InitError}; +use crate::certification::WitnessBuilder; +use crate::{GrowFailed, Memory}; +use sha2::{Digest, Sha256}; + +const MAGIC: [u8; 3] = *b"SCS"; // Short for "stable certified sequence" + +pub type Hash = [u8; 32]; + +type Repr = BaseVec; + +pub struct CertifiedSeq(Repr); + +impl CertifiedSeq { + pub fn new(memory: M) -> Result { + Repr::::new(memory, MAGIC).map(Self) + } + + pub fn init(memory: M) -> Result { + Repr::::init(memory, MAGIC).map(Self) + } + + /// Appends a new entry to the sequence. + /// Returns the logical index of the entry. + pub fn append(&self, bytes: &[u8]) -> Result { + let n = self.0.len(); + let data_hash = leaf_hash(bytes); + let logical_index = if n == 0 { + // The first element is a special case. + self.0.push(&data_hash)?; + 0 + } else { + // Push a temporary internal node. + self.0.push(&[0; 32])?; + if let Err(e) = self.0.push(&data_hash) { + // Remove the temporary node if there is no room for the + // data node. + let _ = self.0.pop(); + return Err(e); + } + + let logical_index = num_leaves(n); + let mut right_hash = leaf_subtree_hash(logical_index, &data_hash); + + let mut i = n + 1; + while let Some(p) = left_parent(i) { + let lc = left_child(p).unwrap(); + let left_hash = self.0.get(lc).unwrap(); + right_hash = if lc % 2 == 0 { + fork_hash(&leaf_subtree_hash(lc / 2, &left_hash), &right_hash) + } else { + fork_hash(&left_hash, &right_hash) + }; + self.0.set(p, &right_hash); + i = p; + } + + logical_index + }; + + debug_assert!(self.0.len() % 2 == 1); + + Ok(logical_index) + } + + pub fn witness_item(&self, logical_index: u64, leaf_data: &[u8]) -> B::Tree { + let n = self.0.len(); + + if n == 0 { + return B::empty(); + } + + let (mut pos, data_witness) = if logical_index < num_leaves(n) { + debug_assert_eq!(self.0.get(logical_index * 2).unwrap(), leaf_hash(leaf_data)); + (logical_index * 2, B::leaf(leaf_data)) + } else { + // Absence proof + (n - 1, B::pruned(self.0.get(n - 1).unwrap())) + }; + + // Build witness bottom-up, starting with the leaf. + let mut witness = B::node(&(pos / 2).to_be_bytes(), data_witness); + loop { + // LOOP INVARIANT: witness is a valid proof for a subtree rooted at pos. + match node_parent(pos, n) { + NodeParent::Root => break, + NodeParent::LeftChildOf(p) => { + let right_hash = self.hash(right_child(p, n).unwrap()); + witness = B::fork(witness, B::pruned(right_hash)); + pos = p; + } + NodeParent::RightChildOf(p) => { + let left_hash = self.hash(left_child(p).unwrap()); + witness = B::fork(B::pruned(left_hash), witness); + pos = p; + } + } + } + witness + } + + fn hash(&self, i: u64) -> Hash { + let hash = self + .0 + .get(i) + .expect("BUG: requested a node that is not in the storage"); + if i % 2 == 0 { + leaf_subtree_hash(i / 2, &hash) + } else { + hash + } + } + + /// Returns the root hash of the sequence. + pub fn root_hash(&self) -> Hash { + match self.root_index() { + Some(i) => self.hash(i), + None => empty_hash(), + } + } + + /// Returns the logical number of entries in the sequence. + pub fn num_entries(&self) -> u64 { + num_leaves(self.0.len()) + } + + /// Returns the index of the root node. + fn root_index(&self) -> Option { + tree_root(self.0.len()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum NodeParent { + Root, + LeftChildOf(u64), + RightChildOf(u64), +} + +fn node_parent(i: u64, n: u64) -> NodeParent { + if Some(i) == tree_root(n) { + return NodeParent::Root; + } + let p = full_tree_parent(i); + if n <= p { + // The full tree parent is outside of the tree. + // The node must be attached to the left parent. + return NodeParent::RightChildOf(left_parent(i).unwrap()); + } + if is_full_tree_left_child(i) { + NodeParent::LeftChildOf(p) + } else { + NodeParent::RightChildOf(p) + } +} + +/// Returns an integer corresponding to the last set bit of n. +fn lsb(n: u64) -> u64 { + n - (n & (n - 1)) +} + +/// Returns the index of the left child for node `i`. +/// Returns None if `i` is a leaf. +fn left_child(i: u64) -> Option { + if i % 2 == 0 { + return None; + } + Some(i - lsb((i + 1) / 2)) +} + +fn full_tree_right_child(i: u64) -> Option { + if i % 2 == 0 { + return None; + } + let b = lsb(i + 1); + Some(i + b - (b >> 1)) +} + +fn right_child(i: u64, n: u64) -> Option { + let candidate = full_tree_right_child(i)?; + if candidate < n { + Some(candidate) + } else { + Some(i + 1 + tree_root(n - i - 1)?) + } +} + +/// Returns the index of the parent for node `i` if the parent is on +/// the left. +fn left_parent(i: u64) -> Option { + if i == 0 { + return None; + } + if i % 2 == 0 { + return Some(i - 1); + } + let lt_size = 2 * lsb((i + 1) / 2) - 1; + if i == lt_size { + return None; + } + Some(i - lt_size - 1) +} + +/// Returns the parent of node i within a full binary tree. +fn full_tree_parent(i: u64) -> u64 { + let b = lsb(i + 1); + (i + b) & !(b << 1) +} + +#[test] +fn test_full_tree_parent() { + let parents = [1, 3, 1, 7, 5, 3, 5, 15, 9, 11, 9, 7, 13, 11, 13]; + for (i, p) in parents.iter().enumerate() { + assert_eq!( + full_tree_parent(i as u64), + *p as u64, + "Expected the parent of {i} to be {p}" + ); + } +} + +fn is_full_tree_left_child(i: u64) -> bool { + i & (lsb(i + 1) << 1) == 0 +} + +/// Returns the number of leaves in a flat tree of the given size. +fn num_leaves(s: u64) -> u64 { + debug_assert!(s == 0 || s % 2 == 1); + // In a flat tree, we must have `s = 2n - 1`, where `n` is the leaf count. + // Then `n = (s + 1) / 2`, which also gives the right answer if `s = 0`. + (s + 1) / 2 +} + +/// Returns the index of the root node in the tree of the specified +/// size. Returns `None` if the tree is empty. +/// +/// Precondition: `s = 0 \/ s % 2 = 1` +/// Postcondition: `s > 0 => tree_root(s) = Some(i): i < s` +fn tree_root(s: u64) -> Option { + debug_assert!(s == 0 || s % 2 == 1); + // The root is always after the first largest full subtree of size + // less than or equal to s. In other words, if `s = 2^k + q`, + // where `q < 2^k`, then the result is `2^k - 1`. + match s { + 0 => None, + 1 => Some(0), + n => Some((1 << n.ilog2()) - 1), + } +} + +/// Returns the hash of an empty tree. +fn empty_hash() -> Hash { + domain_sep("ic-hashtree-empty").finalize().into() +} + +/// Returns the hash of a fork with the given subtrees. +fn fork_hash(lh: &Hash, rh: &Hash) -> Hash { + let mut fork_hasher = domain_sep("ic-hashtree-fork"); + fork_hasher.update(lh); + fork_hasher.update(rh); + fork_hasher.finalize().into() +} + +/// Returns the hash of a leaf containing the given bytes. +fn leaf_hash(bytes: &[u8]) -> Hash { + let mut data_hasher = domain_sep("ic-hashtree-leaf"); + data_hasher.update(bytes); + data_hasher.finalize().into() +} + +/// Returns the hash of a leaf labeled with the given index. +fn leaf_subtree_hash(idx: u64, data_hash: &Hash) -> Hash { + let mut labeled_hasher = domain_sep("ic-hashtree-labeled"); + labeled_hasher.update(idx.to_be_bytes()); + labeled_hasher.update(data_hash); + labeled_hasher.finalize().into() +} + +fn domain_sep(s: &str) -> sha2::Sha256 { + let buf: [u8; 1] = [s.len() as u8]; + let mut h = Sha256::new(); + h.update(&buf[..]); + h.update(s.as_bytes()); + h +} diff --git a/src/certified_seq/tests.rs b/src/certified_seq/tests.rs new file mode 100644 index 00000000..03d6f1cd --- /dev/null +++ b/src/certified_seq/tests.rs @@ -0,0 +1,128 @@ +use super::*; +use crate::certification::WitnessBuilder; +use crate::DefaultMemoryImpl; +use ic_agent::hash_tree::{self, HashTree}; +use proptest::collection::vec as pvec; +use proptest::prelude::*; +use std::collections::VecDeque; + +fn compute_seq_hashes(leaves: &[Vec]) -> Vec { + enum Tree { + Leaf(Hash), + Fork(Hash, Box, Box), + } + + impl Tree { + fn hash(&self) -> &Hash { + match self { + Self::Leaf(h) => h, + Self::Fork(h, _, _) => h, + } + } + + fn flatten(&self, sink: &mut Vec) { + match self { + Self::Leaf(h) => sink.push(*h), + Self::Fork(h, l, r) => { + l.flatten(sink); + sink.push(*h); + r.flatten(sink); + } + } + } + } + + let mut current: VecDeque<_> = leaves + .iter() + .enumerate() + .map(|(i, bs)| Tree::Leaf(leaf_subtree_hash(i as u64, &leaf_hash(bs)))) + .collect(); + + let mut last = VecDeque::new(); + + while current.len() > 1 { + std::mem::swap(&mut current, &mut last); + while let Some(t1) = last.pop_front() { + if let Some(t2) = last.pop_front() { + current.push_back(Tree::Fork( + fork_hash(t1.hash(), t2.hash()), + Box::new(t1), + Box::new(t2), + )); + } else { + current.push_back(t1); + } + } + } + + let mut result = vec![]; + if let Some(t) = current.pop_front() { + t.flatten(&mut result); + } + result +} + +struct IcAgentTreeBuilder; +impl WitnessBuilder for IcAgentTreeBuilder { + type Tree = HashTree>; + + fn empty() -> Self::Tree { + hash_tree::empty() + } + + fn fork(left: Self::Tree, right: Self::Tree) -> Self::Tree { + hash_tree::fork(left, right) + } + + fn node(label: &[u8], child: Self::Tree) -> Self::Tree { + hash_tree::label(label, child) + } + + fn leaf(data: &[u8]) -> Self::Tree { + hash_tree::leaf(data) + } + + fn pruned(hash: [u8; 32]) -> Self::Tree { + hash_tree::pruned(hash) + } +} + +proptest! { + #[test] + fn test_inorder_node_hashes(data in pvec(pvec(any::(), 0..50), 1..32)) { + let expected = compute_seq_hashes(&data); + let seq = CertifiedSeq::new(DefaultMemoryImpl::default()).unwrap(); + for blob in &data { + seq.append(blob).unwrap(); + } + + prop_assert_eq!(seq.0.len(), expected.len() as u64); + + for (i, h) in expected.iter().enumerate() { + let i = i as u64; + let hash = seq.0.get(i).unwrap(); + let actual = if i % 2 == 0 { leaf_subtree_hash(i / 2, &hash) } else { hash }; + prop_assert_eq!( + actual, *h, + "differ at index {}, expected sequence: {:?}", i, &expected + ); + } + } + + #[test] + fn test_witness_generation(data in pvec(pvec(any::(), 0..50), 0..32)) { + let seq = CertifiedSeq::new(DefaultMemoryImpl::default()).unwrap(); + for blob in &data { + seq.append(blob).unwrap(); + } + + for (i, b) in data.iter().enumerate() { + let tree = seq.witness_item::(i as u64, b); + prop_assert_eq!(tree.digest(), seq.root_hash()); + prop_assert_eq!(tree.lookup_path(&[&i.to_be_bytes()]), hash_tree::LookupResult::Found(b)); + } + let missing = data.len() as u64 + 1; + let tree = seq.witness_item::(missing, b""); + prop_assert_eq!(tree.lookup_path(&[&missing.to_be_bytes()]), hash_tree::LookupResult::Absent); + } +} diff --git a/src/certified_seq/tree_tests.rs b/src/certified_seq/tree_tests.rs new file mode 100644 index 00000000..40c585b3 --- /dev/null +++ b/src/certified_seq/tree_tests.rs @@ -0,0 +1,125 @@ +use super::*; + +#[test] +fn test_left_parent() { + for n in [0, 1, 3, 7] { + assert_eq!(left_parent(n), None, "Node {n} does not have a left parent",) + } + for (n, p) in [ + (2, 1), + (4, 3), + (5, 3), + (6, 5), + (8, 7), + (9, 7), + (10, 9), + (11, 7), + (12, 11), + (13, 11), + ] { + assert_eq!( + left_parent(n), + Some(p), + "Expected the parent of node {n} to be {p}", + ); + } +} + +#[test] +fn test_full_tree_right_child() { + let answers = [ + None, + Some(2), + None, + Some(5), + None, + Some(6), + None, + Some(11), + None, + Some(10), + None, + Some(13), + None, + Some(14), + None, + ]; + for (node, answer) in answers.into_iter().enumerate() { + assert_eq!( + full_tree_right_child(node as u64), + answer, + "Failed on node {node}" + ); + } +} + +#[test] +fn test_right_child() { + assert_eq!(right_child(15, 19), Some(17)); + assert_eq!(right_child(11, 13), Some(12)); +} + +#[test] +fn test_left_child() { + assert_eq!(left_child(0), None); + assert_eq!(left_child(1), Some(0)); + assert_eq!(left_child(2), None); + assert_eq!(left_child(3), Some(1)); + assert_eq!(left_child(5), Some(4)); + assert_eq!(left_child(7), Some(3)); + assert_eq!(left_child(9), Some(8)); + assert_eq!(left_child(11), Some(9)); + assert_eq!(left_child(13), Some(12)); + assert_eq!(left_child(15), Some(7)); +} + +#[test] +fn test_node_parent() { + use NodeParent::*; + let answers = [ + LeftChildOf(1), + LeftChildOf(3), + RightChildOf(1), + LeftChildOf(7), + LeftChildOf(5), + RightChildOf(3), + RightChildOf(5), + Root, + LeftChildOf(9), + LeftChildOf(11), + RightChildOf(9), + RightChildOf(7), + RightChildOf(11), + ]; + for (node, answer) in answers.into_iter().enumerate() { + assert_eq!( + node_parent(node as u64, answers.len() as u64), + answer, + "Failed for node {node}" + ); + } +} + +#[test] +fn test_full_tree_is_left_child() { + let table = [1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0]; + for (i, flag) in table.into_iter().enumerate() { + assert_eq!( + is_full_tree_left_child(i as u64), + flag == 1, + "Error for node {i}" + ); + } +} + +#[test] +fn test_tree_root() { + assert_eq!(tree_root(0), None); + assert_eq!(tree_root(1), Some(0)); + assert_eq!(tree_root(3), Some(1)); + assert_eq!(tree_root(5), Some(3)); + assert_eq!(tree_root(7), Some(3)); + assert_eq!(tree_root(9), Some(7)); + assert_eq!(tree_root(11), Some(7)); + assert_eq!(tree_root(13), Some(7)); +} diff --git a/src/lib.rs b/src/lib.rs index 53ab7725..48d6dbac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ mod base_vec; pub mod btreemap; pub mod cell; pub use cell::{Cell as StableCell, Cell}; +pub mod certification; +mod certified_seq; pub mod file_mem; #[cfg(target_arch = "wasm32")] mod ic0_memory; // Memory API for canisters. diff --git a/src/storable/tests.rs b/src/storable/tests.rs index b4ff618a..64812509 100644 --- a/src/storable/tests.rs +++ b/src/storable/tests.rs @@ -28,11 +28,13 @@ proptest! { #[test] fn f64_roundtrip(v in any::()) { - prop_assert_eq!(v, Storable::from_bytes(v.to_bytes())); + let parsed: f64 = Storable::from_bytes(v.to_bytes()); + prop_assert_eq!(v, parsed); } #[test] fn f32_roundtrip(v in any::()) { - prop_assert_eq!(v, Storable::from_bytes(v.to_bytes())); + let parsed: f32 = Storable::from_bytes(v.to_bytes()); + prop_assert_eq!(v, parsed); } }