From db4165e6638782de106dbed4ec41c013d5a5e9b9 Mon Sep 17 00:00:00 2001 From: Alexey Proshutinskiy Date: Mon, 6 Sep 2021 18:13:08 +0300 Subject: [PATCH] add tests --- Cargo.lock | 230 +++++++++++++++++---------------- keypair/src/public_key.rs | 51 ++++++-- keypair/src/signature.rs | 12 +- service/Cargo.toml | 2 +- service/Config.toml | 2 +- service/artifacts/sqlite3.wasm | Bin 907683 -> 907701 bytes service/build.sh | 3 +- service/src/dto.rs | 2 +- service/src/main.rs | 2 +- service/src/results.rs | 49 ++++++- service/src/service_api.rs | 65 ++++++++-- service/src/service_impl.rs | 99 +++++++++++--- service/src/storage_impl.rs | 13 +- service/src/tests.rs | 135 +++++++++++++++---- service/trust-graph.aqua | 20 ++- src/certificate.rs | 43 +++++- src/lib.rs | 2 +- 17 files changed, 534 insertions(+), 196 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fbda802..da54666 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" +checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" [[package]] name = "arrayref" @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake3" @@ -182,9 +182,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cargo_toml" @@ -199,9 +199,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" [[package]] name = "cfg-if" @@ -251,9 +251,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cpufeatures" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] @@ -390,9 +390,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" dependencies = [ "quote", "syn", @@ -537,7 +537,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_bytes", - "sha2 0.9.5", + "sha2 0.9.6", "zeroize", ] @@ -692,7 +692,7 @@ dependencies = [ "libsecp256k1", "log", "multihash", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "pin-project 1.0.8", "prost", "prost-build", @@ -700,7 +700,7 @@ dependencies = [ "ring", "rw-stream-sink", "serde", - "sha2 0.9.5", + "sha2 0.9.6", "smallvec", "thiserror", "unsigned-varint 0.7.0", @@ -742,13 +742,14 @@ dependencies = [ [[package]] name = "fluence-it-types" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5006d09553345421af5dd2334cc945fc34dc2a73d7c1ed842a39a3803699619d" +checksum = "047f670b4807cab8872550a607b1515daff08b3e3bb7576ce8f45971fd811a4e" dependencies = [ "it-to-bytes", "nom", "serde", + "variant_count", "wast", ] @@ -771,7 +772,7 @@ dependencies = [ "serde_bytes", "serde_json", "serde_with", - "sha2 0.9.5", + "sha2 0.9.6", "signature", "thiserror", "zeroize", @@ -795,9 +796,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" dependencies = [ "futures-channel", "futures-core", @@ -810,9 +811,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" dependencies = [ "futures-core", "futures-sink", @@ -820,15 +821,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" [[package]] name = "futures-executor" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c" +checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" dependencies = [ "futures-core", "futures-task", @@ -838,15 +839,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" [[package]] name = "futures-macro" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57" +checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" dependencies = [ "autocfg", "proc-macro-hack", @@ -857,15 +858,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" [[package]] name = "futures-task" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" [[package]] name = "futures-timer" @@ -875,9 +876,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" dependencies = [ "autocfg", "futures-channel", @@ -1138,15 +1139,15 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "js-sys" -version = "0.3.52" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" +checksum = "e4bf49d50e2961077d9c99f4b7997d770a1114f087c3c2e0069b36c13fc2979d" dependencies = [ "wasm-bindgen", ] @@ -1178,9 +1179,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.99" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "libsecp256k1" @@ -1209,9 +1210,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] @@ -1233,13 +1234,13 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "marine-it-generator" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62f29b16bbdb0763a04f8561c954624ee9cd9f558af4e67b95eb00880da11ec" +checksum = "e7b40920a86fb49456f0e94862d56a8e0bfc22489e518d894628da0f3ade03d8" dependencies = [ "cargo_toml", "it-lilo", - "marine-it-parser 0.6.5", + "marine-it-parser 0.6.6", "marine-macro-impl", "once_cell", "serde", @@ -1261,9 +1262,9 @@ dependencies = [ [[package]] name = "marine-it-interfaces" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18c137e51fd52ab7a3652233fc4eaa68e25a6a53d609bf9dd0f2e3bf67adee1" +checksum = "42e229143e72ba20e754de4766ff0d02e0cf176001f7471593f82b16c72dc26d" dependencies = [ "multimap", "wasmer-interface-types-fl", @@ -1288,13 +1289,13 @@ dependencies = [ [[package]] name = "marine-it-parser" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a6606e472587b2e7b759b16d037a4ea951facc2a6650f668f22403978c2442" +checksum = "4154fc98fcfeba65a45d774cff6eeb8bddc8aa66e897f46a74dc95e8823771ea" dependencies = [ "anyhow", "itertools 0.10.1", - "marine-it-interfaces 0.4.0", + "marine-it-interfaces 0.4.1", "marine-module-interface", "nom", "semver 0.11.0", @@ -1346,13 +1347,13 @@ dependencies = [ [[package]] name = "marine-module-interface" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a5936273bebb523ed169863282dbc19fc66bb983c7031c5b8b0556584f2401" +checksum = "035fb5d160a50cbcbe244a343081550f689ceba945d95281bbe207d98bf25586" dependencies = [ "anyhow", "itertools 0.10.1", - "marine-it-interfaces 0.4.0", + "marine-it-interfaces 0.4.1", "nom", "semver 0.11.0", "serde", @@ -1409,8 +1410,8 @@ dependencies = [ "it-lilo", "log", "marine-it-generator", - "marine-it-interfaces 0.4.0", - "marine-it-parser 0.6.5", + "marine-it-interfaces 0.4.1", + "marine-it-parser 0.6.6", "marine-module-info-parser", "marine-module-interface", "marine-utils", @@ -1430,9 +1431,9 @@ dependencies = [ [[package]] name = "marine-sqlite-connector" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7aef66554a852d86eb2ee64fc11651ecb798549d0d8204761866f7fa0a40ebb" +checksum = "c514fdaa43d543a58a01136d3ac4ddcfdb438cb5e25fc164554ab556c2126efa" dependencies = [ "marine-rs-sdk", ] @@ -1484,15 +1485,15 @@ checksum = "8dc5838acba84ce4d802d672afd0814fae0ae7098021ae5b06d975e70d09f812" [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap" @@ -1532,7 +1533,7 @@ dependencies = [ "digest 0.9.0", "generic-array 0.14.4", "multihash-derive", - "sha2 0.9.5", + "sha2 0.9.6", "unsigned-varint 0.5.1", ] @@ -1614,9 +1615,9 @@ dependencies = [ [[package]] name = "object" -version = "0.26.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386" +checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" dependencies = [ "memchr", ] @@ -1667,13 +1668,13 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.4", - "parking_lot_core 0.8.3", + "lock_api 0.4.5", + "parking_lot_core 0.8.5", ] [[package]] @@ -1692,9 +1693,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if 1.0.0", "instant", @@ -1841,9 +1842,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ "unicode-xid", ] @@ -2147,9 +2148,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc_version" @@ -2273,9 +2274,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" +checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" dependencies = [ "itoa", "ryu", @@ -2294,9 +2295,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1569374bd54623ec8bd592cf22ba6e03c0f177ff55fbc8c29a49e296e7adecf" +checksum = "98c1fcca18d55d1763e1c16873c4bde0ac3ef75179a28c7b372917e0494625be" dependencies = [ "darling 0.13.0", "proc-macro2", @@ -2318,9 +2319,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +checksum = "9204c41a1597a8c5af23c82d1c921cb01ec0a4c59e07a9c7306062829a3903f3" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", @@ -2379,9 +2380,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" dependencies = [ "proc-macro2", "quote", @@ -2422,18 +2423,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" dependencies = [ "proc-macro2", "quote", @@ -2490,7 +2491,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "sha2 0.9.5", + "sha2 0.9.6", "signature", "thiserror", ] @@ -2510,7 +2511,7 @@ dependencies = [ "marine-rs-sdk-test", "marine-sqlite-connector", "once_cell", - "parking_lot 0.11.1", + "parking_lot 0.11.2", "rmp-serde", "serde_bencode", "serde_json", @@ -2520,9 +2521,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.13.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "typetag" @@ -2556,12 +2557,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" [[package]] name = "unicode-normalization" @@ -2623,6 +2621,16 @@ dependencies = [ "getrandom 0.2.3", ] +[[package]] +name = "variant_count" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae2faf80ac463422992abf4de234731279c058aaf33171ca70277c98406b124" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "version_check" version = "0.9.3" @@ -2675,9 +2683,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" +checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2685,9 +2693,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" +checksum = "cfe8dc78e2326ba5f845f4b5bf548401604fa20b1dd1d365fb73b6c1d6364041" dependencies = [ "bumpalo", "lazy_static", @@ -2700,9 +2708,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" +checksum = "44468aa53335841d9d6b6c023eaab07c0cd4bddbcfdee3e2bb1e8d2cb8069fef" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2710,9 +2718,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" +checksum = "0195807922713af1e67dc66132c7328206ed9766af3858164fb583eedc25fbad" dependencies = [ "proc-macro2", "quote", @@ -2723,9 +2731,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" +checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29" [[package]] name = "wasmer-clif-backend-fl" @@ -2781,9 +2789,9 @@ dependencies = [ [[package]] name = "wasmer-interface-types-fl" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df960871d756f87237e7630daa0e8453dd48f9e44e0f214e795362a6daa04967" +checksum = "14ba3b5a07989987994b96bf5cc7ac2947005f9ff6123d71b7064232f07d28fa" dependencies = [ "fluence-it-types", "it-lilo", @@ -2926,9 +2934,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.52" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" +checksum = "224b2f6b67919060055ef1a67807367c2066ed520c3862cc013d26cf893a783c" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/keypair/src/public_key.rs b/keypair/src/public_key.rs index 1e0c761..cde4028 100644 --- a/keypair/src/public_key.rs +++ b/keypair/src/public_key.rs @@ -14,16 +14,16 @@ * limitations under the License. */ use crate::ed25519; +use crate::error::{DecodingError, SigningError}; #[cfg(not(target_arch = "wasm32"))] use crate::rsa; use crate::secp256k1; -use crate::error::{DecodingError, SigningError}; use crate::signature::Signature; -use serde::{Deserialize, Serialize}; use crate::key_pair::KeyFormat; -use std::convert::TryFrom; use libp2p_core::PeerId; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; /// The public key of a node's identity keypair. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -42,13 +42,14 @@ impl PublicKey { /// that the signature has been produced by the corresponding /// private key (authenticity), and that the message has not been /// tampered with (integrity). + // TODO: add VerificationError pub fn verify(&self, msg: &[u8], sig: &Signature) -> Result<(), SigningError> { use PublicKey::*; match self { Ed25519(pk) => pk.verify(msg, sig.to_vec()), #[cfg(not(target_arch = "wasm32"))] Rsa(pk) => pk.verify(msg, sig.to_vec()), - Secp256k1(pk) => pk.verify(msg, sig.to_vec()) + Secp256k1(pk) => pk.verify(msg, sig.to_vec()), } } @@ -70,8 +71,12 @@ impl PublicKey { match KeyFormat::try_from(bytes[0])? { KeyFormat::Ed25519 => Ok(PublicKey::Ed25519(ed25519::PublicKey::decode(&bytes[1..])?)), #[cfg(not(target_arch = "wasm32"))] - KeyFormat::Rsa => Ok(PublicKey::Rsa(rsa::PublicKey::from_pkcs1(bytes[1..].to_owned())?)), - KeyFormat::Secp256k1 => Ok(PublicKey::Secp256k1(secp256k1::PublicKey::decode(&bytes[1..])?)), + KeyFormat::Rsa => Ok(PublicKey::Rsa(rsa::PublicKey::from_pkcs1( + bytes[1..].to_owned(), + )?)), + KeyFormat::Secp256k1 => Ok(PublicKey::Secp256k1(secp256k1::PublicKey::decode( + &bytes[1..], + )?)), } } @@ -81,12 +86,14 @@ impl PublicKey { Ed25519(_) => KeyFormat::Ed25519.into(), #[cfg(not(target_arch = "wasm32"))] Rsa(_) => KeyFormat::Rsa.into(), - Secp256k1(_) => KeyFormat::Secp256k1.into() + Secp256k1(_) => KeyFormat::Secp256k1.into(), } } pub fn from_base58(str: &str) -> Result { - let bytes = bs58::decode(str).into_vec().map_err(DecodingError::Base58DecodeError)?; + let bytes = bs58::decode(str) + .into_vec() + .map_err(DecodingError::Base58DecodeError)?; Self::decode(&bytes) } @@ -111,10 +118,14 @@ impl From for PublicKey { use libp2p_core::identity::PublicKey::*; match key { - Ed25519(key) => PublicKey::Ed25519(ed25519::PublicKey::decode(&key.encode()[..]).unwrap()), + Ed25519(key) => { + PublicKey::Ed25519(ed25519::PublicKey::decode(&key.encode()[..]).unwrap()) + } #[cfg(not(target_arch = "wasm32"))] Rsa(key) => PublicKey::Rsa(rsa::PublicKey::from_pkcs1(key.encode_pkcs1()).unwrap()), - Secp256k1(key) => PublicKey::Secp256k1(secp256k1::PublicKey::decode(&key.encode()[..]).unwrap()), + Secp256k1(key) => { + PublicKey::Secp256k1(secp256k1::PublicKey::decode(&key.encode()[..]).unwrap()) + } } } } @@ -124,16 +135,28 @@ impl From for libp2p_core::identity::PublicKey { use libp2p_core::identity as libp2p_identity; match key { - PublicKey::Ed25519(key) => libp2p_identity::PublicKey::Ed25519(libp2p_identity::ed25519::PublicKey::decode(&key.encode()[..]).unwrap()), + PublicKey::Ed25519(key) => libp2p_identity::PublicKey::Ed25519( + libp2p_identity::ed25519::PublicKey::decode(&key.encode()[..]).unwrap(), + ), #[cfg(not(target_arch = "wasm32"))] - PublicKey::Rsa(key) => libp2p_identity::PublicKey::Rsa(libp2p_identity::rsa::PublicKey::decode_x509(&key.encode_x509()).unwrap()), - PublicKey::Secp256k1(key) => libp2p_identity::PublicKey::Secp256k1(libp2p_identity::secp256k1::PublicKey::decode(&key.encode()[..]).unwrap()), + PublicKey::Rsa(key) => libp2p_identity::PublicKey::Rsa( + libp2p_identity::rsa::PublicKey::decode_x509(&key.encode_x509()).unwrap(), + ), + PublicKey::Secp256k1(key) => libp2p_identity::PublicKey::Secp256k1( + libp2p_identity::secp256k1::PublicKey::decode(&key.encode()[..]).unwrap(), + ), } } } pub fn peer_id_to_fluence_pk(peer_id: libp2p_core::PeerId) -> eyre::Result { - Ok(peer_id.as_public_key().ok_or(eyre::eyre!("public key is not inlined in peer id: {}", peer_id))?.into()) + Ok(peer_id + .as_public_key() + .ok_or(eyre::eyre!( + "public key is not inlined in peer id: {}", + peer_id + ))? + .into()) } #[cfg(test)] diff --git a/keypair/src/signature.rs b/keypair/src/signature.rs index 2383473..bb05da8 100644 --- a/keypair/src/signature.rs +++ b/keypair/src/signature.rs @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -use crate::ed25519; +use crate::{ed25519, PublicKey}; use crate::secp256k1; #[cfg(not(target_arch = "wasm32"))] use crate::rsa; @@ -107,6 +107,16 @@ impl Signature { _ => Err(DecodingError::RawSignatureUnsupportedType(raw_signature.sig_type)), } } + + pub fn from_bytes_with_public_key(pk: &PublicKey, bytes: Vec) -> Self { + use PublicKey::*; + match pk { + Ed25519(_) => Signature::Ed25519(ed25519::Signature(bytes)), + #[cfg(not(target_arch = "wasm32"))] + Rsa(_) => Signature::Rsa(rsa::Signature(bytes)), + Secp256k1(_) => Signature::Secp256k1(secp256k1::Signature(bytes)) + } + } } #[cfg(test)] diff --git a/service/Cargo.toml b/service/Cargo.toml index 01dfe1b..25f47c5 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" trust-graph = { version = "0.2.6", path = "../." } fluence-keypair = { version = "0.3.0", path = "../keypair" } marine-rs-sdk = { version = "0.6.11", features = ["logger"] } -marine-sqlite-connector = "0.5.0" +marine-sqlite-connector = "0.5.1" libp2p-core = { package = "fluence-fork-libp2p-core", version = "0.27.2", features = ["secp256k1"]} diff --git a/service/Config.toml b/service/Config.toml index 51128af..d0aa9c3 100644 --- a/service/Config.toml +++ b/service/Config.toml @@ -3,7 +3,7 @@ modules_dir = "artifacts/" [[module]] name = "sqlite3" mem_pages_count = 100 - logger_enabled = false + logger_enabled = true [[module]] name = "trust-graph" diff --git a/service/artifacts/sqlite3.wasm b/service/artifacts/sqlite3.wasm index cd72403109d2ea360e85f7c9798f06f7efd15f8a..36651f2b81f9ae7536e05c9e3851a36769ddcc88 100644 GIT binary patch delta 37687 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 Kp1piWxBmqaGPyVa delta 37943 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 diff --git a/service/build.sh b/service/build.sh index f1ef3f1..7efe94e 100755 --- a/service/build.sh +++ b/service/build.sh @@ -5,7 +5,6 @@ set -o errexit -o nounset -o pipefail cd "$(dirname "$0")" # build trust-graph.wasm -#cargo update marine build --release # copy .wasm to artifacts @@ -14,7 +13,7 @@ mkdir -p artifacts cp ../target/wasm32-wasi/release/trust-graph.wasm artifacts/ # download SQLite 3 to use in tests -curl -L https://github.com/fluencelabs/sqlite/releases/download/v0.14.0_w/sqlite3.wasm -o artifacts/sqlite3.wasm +curl -L https://github.com/fluencelabs/sqlite/releases/download/v0.15.0_w/sqlite3.wasm -o artifacts/sqlite3.wasm # generate Aqua bindings marine aqua artifacts/trust-graph.wasm -s TrustGraph -i trust-graph > trust-graph.aqua diff --git a/service/src/dto.rs b/service/src/dto.rs index 427fbfd..22b97b5 100644 --- a/service/src/dto.rs +++ b/service/src/dto.rs @@ -91,7 +91,7 @@ impl TryFrom for trust_graph::Trust { impl From for Trust { fn from(t: trust_graph::Trust) -> Self { - let issued_for = bs58::encode(t.issued_for.encode()).into_string(); + let issued_for = t.issued_for.to_peer_id().to_base58(); let raw_signature = t.signature.get_raw_signature(); let signature = bs58::encode(raw_signature.bytes).into_string(); let expires_at = t.expires_at.as_secs(); diff --git a/service/src/main.rs b/service/src/main.rs index a32f0d1..ace1390 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -9,7 +9,7 @@ mod tests; pub fn main() { WasmLoggerBuilder::new() - .with_log_level(log::LevelFilter::Info) + .with_log_level(log::LevelFilter::Trace) .build() .unwrap(); } diff --git a/service/src/results.rs b/service/src/results.rs index 8c98f48..e93dd32 100644 --- a/service/src/results.rs +++ b/service/src/results.rs @@ -105,12 +105,12 @@ impl From, ServiceError>> for GetTrustMetadataResult { Ok(res) => GetTrustMetadataResult { success: true, error: "".to_string(), - result: res + result: res, }, Err(e) => GetTrustMetadataResult { success: false, error: format!("{}", e), - result: vec![] + result: vec![], }, } } @@ -139,3 +139,48 @@ impl From> for IssueTrustResult { } } } + +#[marine] +pub struct VerifyTrustResult { + pub success: bool, + pub error: String, +} + +impl From> for VerifyTrustResult { + fn from(result: Result<(), ServiceError>) -> Self { + match result { + Ok(()) => VerifyTrustResult { + success: true, + error: "".to_string(), + }, + Err(e) => VerifyTrustResult { + success: false, + error: format!("{}", e), + }, + } + } +} + +#[marine] +pub struct IssueCertificateResult { + pub success: bool, + pub error: String, + pub cert: Certificate, +} + +impl From> for IssueCertificateResult { + fn from(result: Result) -> Self { + match result { + Ok(cert) => IssueCertificateResult { + success: true, + error: "".to_string(), + cert, + }, + Err(e) => IssueCertificateResult { + success: false, + error: format!("{}", e), + cert: Certificate { chain: vec![] }, + }, + } + } +} diff --git a/service/src/service_api.rs b/service/src/service_api.rs index 0a69e54..edc36b3 100644 --- a/service/src/service_api.rs +++ b/service/src/service_api.rs @@ -1,7 +1,14 @@ -use crate::dto::Certificate; -use crate::results::{AddRootResult, AllCertsResult, InsertResult, WeightResult, GetTrustMetadataResult}; -use crate::service_impl::{add_root_impl, get_all_certs_impl, get_weight_impl, insert_cert_impl, insert_cert_impl_raw, get_trust_metadata_imp}; -use marine_rs_sdk::{CallParameters, marine}; +use crate::dto::{Certificate, Trust}; +use crate::results::{ + AddRootResult, AllCertsResult, GetTrustMetadataResult, InsertResult, IssueCertificateResult, + IssueTrustResult, VerifyTrustResult, WeightResult, +}; +use crate::service_impl::{ + add_root_impl, get_all_certs_impl, get_trust_metadata_imp, get_weight_impl, insert_cert_impl, + insert_cert_impl_raw, issue_certificate_with_trust_checked_impl, + issue_root_certificate_checked_impl, issue_trust_impl, verify_trust_impl, +}; +use marine_rs_sdk::{marine, CallParameters}; #[marine] /// add a certificate in string representation to trust graph if it is valid @@ -18,11 +25,13 @@ fn insert_cert(certificate: Certificate, current_time: u64) -> InsertResult { insert_cert_impl(certificate, current_time).into() } +// TODO: pass current timestamp, return only valid, delete expired #[marine] fn get_weight(peer_id: String) -> WeightResult { get_weight_impl(peer_id).into() } +// TODO: pass current timestamp, return only valid, delete expired #[marine] fn get_all_certs(issued_for: String) -> AllCertsResult { get_all_certs_impl(issued_for).into() @@ -37,18 +46,52 @@ fn add_root(peer_id: String, weight: u32) -> AddRootResult { add_root_impl(peer_id, weight).into() } else { return AddRootResult { - success: true, + success: false, error: "Root could add only a host of trust graph service".to_string(), }; } } #[marine] -fn get_trust_metadata(peer_id: String, expires_at: u64, issued_at: u64) -> GetTrustMetadataResult { - get_trust_metadata_imp(peer_id, expires_at, issued_at).into() +fn get_trust_metadata( + issued_for_peer_id: String, + expires_at: u64, + issued_at: u64, +) -> GetTrustMetadataResult { + get_trust_metadata_imp(issued_for_peer_id, expires_at, issued_at).into() } -// #[marine] -// fn issue_trust(peer_id: String, expires_at: u64, issued_at: u64, signed_metadata: Vec, sig_type: String) -> IssueTrustResult { -// issue_trust_impl(peer_id, expires_at, issued_at, signed_metadata).into() -// } \ No newline at end of file +#[marine] +fn issue_trust( + issued_for_peer_id: String, + expires_at: u64, + issued_at: u64, + signed_metadata: Vec, +) -> IssueTrustResult { + issue_trust_impl(issued_for_peer_id, expires_at, issued_at, signed_metadata).into() +} + +// TODO: use "peer" "timestamp_sec" and check tetraplets +#[marine] +fn verify_trust(trust: Trust, issuer_peer_id: String, cur_time: u64) -> VerifyTrustResult { + verify_trust_impl(trust, issuer_peer_id, cur_time).into() +} + +#[marine] +fn issue_root_certificate_checked( + root_trust: Trust, + issued_trust: Trust, + cur_time: u64, +) -> IssueCertificateResult { + issue_root_certificate_checked_impl(root_trust, issued_trust, cur_time).into() +} + +#[marine] +fn issue_certificate_with_trust_checked( + cert: Certificate, + trust: Trust, + issued_by_peer_id: String, + cur_time: u64, +) -> IssueCertificateResult { + issue_certificate_with_trust_checked_impl(cert, trust, issued_by_peer_id, cur_time).into() +} diff --git a/service/src/service_impl.rs b/service/src/service_impl.rs index 657d31e..b4e29c4 100644 --- a/service/src/service_impl.rs +++ b/service/src/service_impl.rs @@ -1,14 +1,14 @@ use crate::dto::{Certificate, DtoConversionError, Trust}; use crate::storage_impl::get_data; use fluence_keypair::error::DecodingError; -use fluence_keypair::PublicKey; +use fluence_keypair::public_key::peer_id_to_fluence_pk; +use fluence_keypair::{PublicKey, Signature}; +use libp2p_core::PeerId; use std::convert::{Into, TryInto}; use std::str::FromStr; use std::time::Duration; use thiserror::Error as ThisError; -use trust_graph::{CertificateError, TrustGraphError}; -use fluence_keypair::public_key::peer_id_to_fluence_pk; -use libp2p_core::PeerId; +use trust_graph::{current_time, CertificateError, TrustError, TrustGraphError}; #[derive(ThisError, Debug)] pub enum ServiceError { @@ -40,14 +40,22 @@ pub enum ServiceError { #[source] DtoConversionError, ), + #[error("{0}")] + TrustError( + #[from] + #[source] + TrustError, + ), } fn parse_peer_id(peer_id: String) -> Result { - libp2p_core::PeerId::from_str(&peer_id).map_err(|e| ServiceError::PeerIdParseError(format!("{:?}", e))) + libp2p_core::PeerId::from_str(&peer_id) + .map_err(|e| ServiceError::PeerIdParseError(format!("{:?}", e))) } fn extract_public_key(peer_id: String) -> Result { - peer_id_to_fluence_pk(parse_peer_id(peer_id)?).map_err(|e| ServiceError::PublicKeyExtractionError(e.to_string())) + peer_id_to_fluence_pk(parse_peer_id(peer_id)?) + .map_err(|e| ServiceError::PublicKeyExtractionError(e.to_string())) } pub fn get_weight_impl(peer_id: String) -> Result, ServiceError> { @@ -99,15 +107,76 @@ pub fn add_root_impl(peer_id: String, weight: u32) -> Result<(), ServiceError> { Ok(()) } -pub fn get_trust_metadata_imp(peer_id: String, expires_at: u64, issued_at: u64) -> Result, ServiceError> { +pub fn get_trust_metadata_imp( + peer_id: String, + expires_at: u64, + issued_at: u64, +) -> Result, ServiceError> { let public_key = extract_public_key(peer_id)?; - Ok(trust_graph::Trust::metadata_bytes(&public_key, - Duration::from_secs(expires_at), - Duration::from_secs(issued_at))) + Ok(trust_graph::Trust::metadata_bytes( + &public_key, + Duration::from_secs(expires_at), + Duration::from_secs(issued_at), + )) } -// pub fn issue_trust_impl(peer_id: String, expires_at: u64, issued_at: u64, signed_metadata: Vec) -> Result { -// let public_key = extract_public_key(peer_id)?; -// trust_graph::Trust::new(public_key, Duration::from_secs(expires_at), Duration::from_secs(issued_at), -// ) -// } \ No newline at end of file +pub fn issue_trust_impl( + peer_id: String, + expires_at: u64, + issued_at: u64, + signed_metadata: Vec, +) -> Result { + let public_key = extract_public_key(peer_id)?; + let expires_at = Duration::from_secs(expires_at); + let issued_at = Duration::from_secs(issued_at); + let signature = Signature::from_bytes_with_public_key(&public_key, signed_metadata); + Ok(Trust::from(trust_graph::Trust::new( + public_key, expires_at, issued_at, signature, + ))) +} + +pub fn verify_trust_impl( + trust: Trust, + issuer_peer_id: String, + cur_time: u64, +) -> Result<(), ServiceError> { + let public_key = extract_public_key(issuer_peer_id)?; + trust_graph::Trust::verify( + &trust.try_into()?, + &public_key, + Duration::from_secs(cur_time), + )?; + + Ok(()) +} + +pub fn issue_root_certificate_checked_impl( + root_trust: Trust, + issued_trust: Trust, + cur_time: u64, +) -> Result { + trust_graph::Certificate::new_from_root_trust( + root_trust.try_into()?, + issued_trust.try_into()?, + Duration::from_secs(cur_time), + ) + .map(|c| c.into()) + .map_err(ServiceError::CertError) +} + +pub fn issue_certificate_with_trust_checked_impl( + cert: Certificate, + trust: Trust, + issued_by: String, + cur_time: u64, +) -> Result { + let public_key = extract_public_key(issued_by)?; + trust_graph::Certificate::issue_with_trust( + public_key, + trust.try_into()?, + &cert.try_into()?, + Duration::from_secs(cur_time), + ) + .map(|c| c.into()) + .map_err(ServiceError::CertError) +} diff --git a/service/src/storage_impl.rs b/service/src/storage_impl.rs index 8f09562..4314461 100644 --- a/service/src/storage_impl.rs +++ b/service/src/storage_impl.rs @@ -3,15 +3,15 @@ // if there is an older trust - don't add received trust use crate::storage_impl::SQLiteStorageError::{ - PublicKeyNotFound, PublicKeyConversion, PublicKeyFromStr, TrustNodeConversion, + PublicKeyConversion, PublicKeyFromStr, PublicKeyNotFound, TrustNodeConversion, WeightConversionDB, }; use core::convert::TryFrom; +use fluence_keypair::public_key::PublicKey; use marine_sqlite_connector; use marine_sqlite_connector::Connection; use marine_sqlite_connector::Error as InternalSqliteError; use marine_sqlite_connector::Value; -use fluence_keypair::public_key::PublicKey; use once_cell::sync::OnceCell; use parking_lot::Mutex; use rmp_serde::decode::Error as RmpDecodeError; @@ -128,14 +128,13 @@ impl Storage for SQLiteStorage { fn insert(&mut self, pk: PK, node: TrustNode) -> Result<(), Self::Error> { let mut cursor = self .connection - .prepare("INSERT OR REPLACE INTO trustnodes VALUES (?, ?)")? - .cursor(); + .prepare("INSERT OR REPLACE INTO trustnodes VALUES (?, ?)")?; let tn_vec = rmp_serde::to_vec(&node)?; - log::info!("insert: {:?}", tn_vec); - - cursor.bind(&[Value::String(format!("{}", pk)), Value::Binary(tn_vec)])?; + let p = format!("{}", pk); + cursor.bind(1, &Value::String(p))?; + cursor.bind(2, &Value::Binary(tn_vec))?; cursor.next()?; Ok({}) diff --git a/service/src/tests.rs b/service/src/tests.rs index b465719..31136c1 100644 --- a/service/src/tests.rs +++ b/service/src/tests.rs @@ -14,34 +14,117 @@ * limitations under the License. */ +// TODO: clear DB before every test, run in 1 thread #[cfg(test)] mod tests { - use marine_rs_sdk_test::marine_test; use fluence_keypair; + use fluence_keypair::KeyPair; + use marine_rs_sdk_test::marine_test; use std::time::Duration; - // #[marine_test(config_path = "../Config.toml", modules_dir = "../artifacts/")] - // fn test() { - // - // let root_kp = Keypair:: - // let root_kp2 = KeyPair::generate(); - // let second_kp = KeyPair::generate(); - // - // let expires_at = Duration::new(15, 15); - // let issued_at = Duration::new(5, 5); - // - // let cert = trust_graph::Certificate::issue_root( - // &root_kp, - // second_kp.public_key(), - // expires_at, - // issued_at, - // ); - // trast_graph.add_root(root_kp.public().into(), 0).unwrap(); - // tg.add_root_weight(root_kp2.public().into(), 1).unwrap(); - // tg.add(cert, Duration::new(10, 10)).unwrap(); - // - // let a = tg.get(second_kp.public_key()).unwrap(); - // let str = format!("{:?}", a); - // log::info!("{}", &str); - // } -} \ No newline at end of file + macro_rules! issue_trust { + ($trust_graph:expr, $issuer_kp:expr, $issued_peer_id: expr, $expires_at:expr, $issued_at: expr) => {{ + let trust_metadata_result = $trust_graph.get_trust_metadata( + $issued_peer_id.to_base58(), + $expires_at, + $issued_at, + ); + assert_result!(trust_metadata_result); + + let metadata = trust_metadata_result.result; + let signed_metadata = $issuer_kp.sign(&metadata).unwrap().to_vec().to_vec(); + let trust_result = $trust_graph.issue_trust( + $issued_peer_id.to_base58(), + $expires_at, + $issued_at, + signed_metadata.to_vec(), + ); + assert_result!(trust_result); + + trust_result.trust + }}; + } + + macro_rules! assert_result { + ($result:expr) => {{ + assert!($result.success, "{:?}", $result.error); + }}; + } + + #[marine_test(config_path = "../Config.toml", modules_dir = "../artifacts/")] + fn issue_trust_test() { + let issuer_kp = KeyPair::generate_ed25519(); + let issued_peer_id = KeyPair::generate_ed25519().get_peer_id(); + let issued_at = 0u64; + let expires_at = 10u64; + let trust = issue_trust!( + trust_graph, + issuer_kp, + issued_peer_id, + expires_at, + issued_at + ); + let verify_result = trust_graph.verify_trust(trust, issuer_kp.get_peer_id().to_base58(), 0); + assert_result!(verify_result); + } + + #[marine_test(config_path = "../Config.toml", modules_dir = "../artifacts/")] + fn issue_cert_test() { + let issuer_kp = KeyPair::generate_ed25519(); + let issued_peer_id = KeyPair::generate_ed25519().get_peer_id(); + let issued_at = 0u64; + let expires_at = 10u64; + let root_trust = issue_trust!( + trust_graph, + issuer_kp, + issuer_kp.get_peer_id(), + expires_at, + issued_at + ); + let trust = issue_trust!( + trust_graph, + issuer_kp, + issued_peer_id, + expires_at, + issued_at + ); + + let cert_result = trust_graph.issue_root_certificate_checked(root_trust, trust, 0u64); + assert_result!(cert_result); + } + + #[marine_test(config_path = "../Config.toml", modules_dir = "../artifacts/")] + fn extend_cert_test() { + let issuer_kp = KeyPair::generate_ed25519(); + let issued_peer_id = KeyPair::generate_ed25519().get_peer_id(); + let issued_at = 0u64; + let expires_at = 10u64; + let root_trust = issue_trust!( + trust_graph, + issuer_kp, + issuer_kp.get_peer_id(), + expires_at, + issued_at + ); + let trust = issue_trust!( + trust_graph, + issuer_kp, + issued_peer_id, + expires_at, + issued_at + ); + + let cert_result = trust_graph.issue_root_certificate_checked(root_trust, trust, 0u64); + assert_result!(cert_result); + println!("{:?}", cert_result.cert); + + assert_result!(trust_graph.add_root(issuer_kp.get_peer_id().to_base58(), 300)); + + let insert_res = trust_graph.insert_cert(cert_result.cert, 0u64); + assert_result!(insert_res); + let all_certs_res = trust_graph.get_all_certs(issued_peer_id.to_base58()); + assert_result!(all_certs_res); + assert_eq!(all_certs_res.certificates.len(), 1); + println!("{:?}", all_certs_res.certificates); + } +} diff --git a/service/trust-graph.aqua b/service/trust-graph.aqua index 98ff6fb..df930df 100644 --- a/service/trust-graph.aqua +++ b/service/trust-graph.aqua @@ -26,6 +26,20 @@ data InsertResult: success: bool error: string +data IssueCertificateResult: + success: bool + error: string + cert: Certificate + +data IssueTrustResult: + success: bool + error: string + trust: Trust + +data VerifyTrustResult: + success: bool + error: string + data WeightResult: success: bool weight: []u32 @@ -34,7 +48,11 @@ data WeightResult: service TrustGraph("trust-graph"): add_root(peer_id: string, weight: u32) -> AddRootResult get_all_certs(issued_for: string) -> AllCertsResult - get_trust_metadata(peer_id: string, expires_at: u64, issued_at: u64) -> GetTrustMetadataResult + get_trust_metadata(issued_for_peer_id: string, expires_at: u64, issued_at: u64) -> GetTrustMetadataResult get_weight(peer_id: string) -> WeightResult insert_cert(certificate: Certificate, current_time: u64) -> InsertResult insert_cert_raw(certificate: string, current_time: u64) -> InsertResult + issue_certificate_with_trust_checked(cert: Certificate, trust: Trust, issued_by_peer_id: string, cur_time: u64) -> IssueCertificateResult + issue_root_certificate_checked(root_trust: Trust, issued_trust: Trust, cur_time: u64) -> IssueCertificateResult + issue_trust(issued_for_peer_id: string, expires_at: u64, issued_at: u64, signed_metadata: []u8) -> IssueTrustResult + verify_trust(trust: Trust, issuer_peer_id: string, cur_time: u64) -> VerifyTrustResult diff --git a/src/certificate.rs b/src/certificate.rs index 81d0793..cea1290 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -74,6 +74,47 @@ impl Certificate { Self { chain } } + pub fn new_from_root_trust(root_trust: Trust, issued_trust: Trust, cur_time: Duration) -> Result { + Trust::verify(&root_trust, &root_trust.issued_for, cur_time).map_err(MalformedRoot)?; + Trust::verify(&issued_trust, &root_trust.issued_for, cur_time).map_err(|e| VerificationError(1, e))?; + + Ok(Self { chain: vec![root_trust, issued_trust] }) + } + + pub fn issue_with_trust(issued_by: PublicKey, trust: Trust, extend_cert: &Certificate, cur_time: Duration) -> Result { + if trust.expires_at.lt(&trust.issued_at) { + return Err(ExpirationError { + expires_at: format!("{:?}", trust.expires_at), + issued_at: format!("{:?}", trust.issued_at), + }); + } + + Certificate::verify(extend_cert, &[extend_cert.chain[0].issued_for.clone()], cur_time)?; + // check if `issued_by` is allowed to issue a certificate (i.e., there’s a trust for it in a chain) + let mut previous_trust_num: i32 = -1; + for pk_id in 0..extend_cert.chain.len() { + if extend_cert.chain[pk_id].issued_for == issued_by { + previous_trust_num = pk_id as i32; + } + } + + if previous_trust_num == -1 { + return Err(KeyInCertificateError); + }; + + // splitting old chain to add new trust after given public key + let mut new_chain = extend_cert + .chain + .split_at((previous_trust_num + 1) as usize) + .0 + .to_vec(); + + new_chain.push(trust); + + Ok(Self { chain: new_chain }) + } + + /// Creates new certificate with root trust (self-signed public key) from a key pair. #[allow(dead_code)] pub fn issue_root( @@ -401,7 +442,7 @@ mod tests { ); assert_eq!(new_cert.is_ok(), true); let new_cert = new_cert.unwrap(); - + println!("cert is\n{}", new_cert.to_string()); assert_eq!(new_cert.chain.len(), 3); diff --git a/src/lib.rs b/src/lib.rs index 99a16eb..a63c1cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ pub use crate::certificate::{Certificate, CertificateError}; pub use crate::misc::current_time; pub use crate::public_key_hashable::PublicKeyHashable; pub use crate::revoke::Revoke; -pub use crate::trust::Trust; +pub use crate::trust::{Trust, TrustError}; pub use crate::trust_graph::{TrustGraph, TrustGraphError, Weight}; pub use crate::trust_graph_storage::{Storage, StorageError, InMemoryStorage, InMemoryStorageError}; pub use crate::trust_node::{Auth, TrustNode};