From 0423757084764b73bef8d71d2193c1a2208ecd1b Mon Sep 17 00:00:00 2001 From: Daniyar Itegulov Date: Fri, 17 Nov 2023 21:24:12 +1100 Subject: [PATCH] implement signing --- Cargo.lock | 330 +++++++++++------- contract/src/lib.rs | 42 ++- integration-tests/Cargo.toml | 27 +- integration-tests/src/env/containers.rs | 7 +- integration-tests/src/indexer.rs | 100 ++++++ integration-tests/src/lib.rs | 1 + .../src/multichain/containers.rs | 3 +- integration-tests/src/multichain/local.rs | 3 +- integration-tests/tests/lib.rs | 47 ++- integration-tests/tests/multichain/mod.rs | 69 ++++ node/Cargo.toml | 7 +- node/src/cli.rs | 16 +- node/src/indexer.rs | 116 ++++-- node/src/protocol/consensus.rs | 23 +- node/src/protocol/contract.rs | 24 +- node/src/protocol/cryptography.rs | 36 ++ node/src/protocol/message.rs | 117 ++++++- node/src/protocol/mod.rs | 24 +- node/src/protocol/presignature.rs | 66 +++- node/src/protocol/signature.rs | 311 +++++++++++++++++ node/src/protocol/state.rs | 18 +- node/src/protocol/triple.rs | 21 +- node/src/rpc_client.rs | 1 + node/src/types.rs | 3 +- 24 files changed, 1159 insertions(+), 253 deletions(-) create mode 100644 integration-tests/src/indexer.rs create mode 100644 node/src/protocol/signature.rs diff --git a/Cargo.lock b/Cargo.lock index 58a12e8ab..b4a17e058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,6 +252,17 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "assert-json-diff" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259cbe96513d2f1073027a259fc2ca917feb3026a5a8d984e3628e490255cc0" +dependencies = [ + "extend", + "serde", + "serde_json", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -278,9 +289,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f658e2baef915ba0f26f1f7c42bfb8e12f532a01f449a090ded75ae7a07e9ba2" +checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" dependencies = [ "flate2", "futures-core", @@ -291,15 +302,15 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0" +checksum = "fc5ea910c42e5ab19012bab31f53cb4d63d54c3a27730f9a833a88efcf4bb52d" dependencies = [ - "async-lock 2.8.0", + "async-lock 3.1.1", "async-task", "concurrent-queue", "fastrand 2.0.1", - "futures-lite 1.13.0", + "futures-lite 2.0.1", "slab", ] @@ -341,14 +352,14 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ed9d5715c2d329bf1b4da8d60455b99b187f27ba726df2883799af9af60997" dependencies = [ - "async-lock 3.1.0", + "async-lock 3.1.1", "cfg-if 1.0.0", "concurrent-queue", "futures-io", "futures-lite 2.0.1", "parking", "polling 3.3.0", - "rustix 0.38.23", + "rustix 0.38.25", "slab", "tracing", "waker-fn", @@ -366,9 +377,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.1.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb2ab2aa8a746e221ab826c73f48bc6ba41be6763f0855cb249eb6d154cf1d7" +checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105" dependencies = [ "event-listener 3.1.0", "event-listener-strategy", @@ -399,7 +410,7 @@ dependencies = [ "cfg-if 1.0.0", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.23", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -415,7 +426,7 @@ dependencies = [ "cfg-if 1.0.0", "futures-core", "futures-io", - "rustix 0.38.23", + "rustix 0.38.25", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -500,9 +511,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "aws-config" -version = "0.53.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741327a7f70e6e639bdb5061964c66250460c70ad3f59c3fe2a3a64ac1484e33" +checksum = "3c3d1e2a1f1ab3ac6c4b884e37413eaa03eb9d901e4fc68ee8f5c1d49721680e" dependencies = [ "aws-credential-types", "aws-http", @@ -529,9 +540,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "0.53.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f99dd587a46af58f8cf37773687ecec19d0373a5954942d7e0f405751fe2369" +checksum = "bb0696a0523a39a19087747e4dafda0362dc867531e3d72a3f195564c84e5e08" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -542,9 +553,9 @@ dependencies = [ [[package]] name = "aws-endpoint" -version = "0.53.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13fdfc00c57d95e10bcf83d2331c4ae9ca460ca84dc983b2cdd692de87640389" +checksum = "80a4f935ab6a1919fbfd6102a80c4fccd9ff5f47f94ba154074afe1051903261" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -556,9 +567,9 @@ dependencies = [ [[package]] name = "aws-http" -version = "0.53.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74cdac70481d144bf7001c27884b95ee12c8f62e61db90320d59b673ae121cb8" +checksum = "82976ca4e426ee9ca3ffcf919d9b2c8d14d0cd80d43cc02173737a8f07f28d4d" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -575,9 +586,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae411cb03ea6df0d4c4340a0d3c15cab7b19715d091f76c5629f31acd6403f3" +checksum = "1533be023eeac69668eb718b1c48af7bd5e26305ed770553d2877ab1f7507b68" dependencies = [ "aws-credential-types", "aws-endpoint", @@ -610,9 +621,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d2fb56182ac693a19364cc0bde22d95aef9be3663bf9b906ffbd0ab0a7c7d1" +checksum = "ca0119bacf0c42f587506769390983223ba834e605f049babe514b2bd646dbb2" dependencies = [ "aws-credential-types", "aws-endpoint", @@ -630,14 +641,13 @@ dependencies = [ "regex", "tokio-stream", "tower", - "url 2.4.1", ] [[package]] name = "aws-sdk-sts" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70adf3e9518c8d6d14f1239f6af04c019ffd260ab791e17deb11f1bce6a9f76" +checksum = "270b6a33969ebfcb193512fbd5e8ee5306888ad6c6d5d775cdbfb2d50d94de26" dependencies = [ "aws-credential-types", "aws-endpoint", @@ -657,14 +667,13 @@ dependencies = [ "regex", "tower", "tracing", - "url 2.4.1", ] [[package]] name = "aws-sig-auth" -version = "0.53.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22af7f6515f8b51dabef87df1d901c9734e4e367791c6d0e1082f9f31528120e" +checksum = "660a02a98ab1af83bd8d714afbab2d502ba9b18c49e7e4cddd6bf8837ff778cb" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -677,9 +686,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "0.53.2" +version = "0.54.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14500f741fb73a3c6cb173f8d96b433319a0e27c370a4e783b9ad693fc86210e" +checksum = "86529e7b64d902efea8fff52c1b2529368d04f90305cf632729e3713f6b57dc0" dependencies = [ "aws-smithy-eventstream", "aws-smithy-http", @@ -698,9 +707,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9900be224962d65a626072d8777f847ae5406c07547f0dc14c60048978c4b" +checksum = "63c712a28a4f2f2139759235c08bf98aca99d4fdf1b13c78c5f95613df0a5db9" dependencies = [ "futures-util", "pin-project-lite", @@ -710,9 +719,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e9e4d3c2296bcec2c03f9f769ac9b2424d972c2fe7afc0b59235447ac3a5c3" +checksum = "a3875fb4b28606a5368a048016a28c15707f2b21238d5b2e4a23198f590e92c4" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -731,13 +740,14 @@ dependencies = [ [[package]] name = "aws-smithy-client" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710ca0f8dacddda5fbcaf5c3cd9d02da7913fd463a2ee9555b617bf168bedacb" +checksum = "104ca17f56cde00a10207169697dfe9c6810db339d52fb352707e64875b30a44" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-http-tower", + "aws-smithy-protocol-test", "aws-smithy-types", "bytes", "fastrand 1.9.0", @@ -747,6 +757,7 @@ dependencies = [ "hyper-rustls 0.23.2", "lazy_static", "pin-project-lite", + "serde", "tokio", "tower", "tracing", @@ -754,9 +765,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d1ff11ee22de3581114b60d4ae8e700638dacb5b5bbe6769726e251e6c3f20a" +checksum = "ac250d8c0e42af0097a6837ffc5a6fb9f8ba4107bb53124c047c91bc2a58878f" dependencies = [ "aws-smithy-types", "bytes", @@ -765,9 +776,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29dcab29afbea7726f5c10c7be0c38666d7eb07db551580b3b26ed7cfb5d1935" +checksum = "873f316f1833add0d3aa54ed1b0cd252ddd88c792a0cf839886400099971e844" dependencies = [ "aws-smithy-eventstream", "aws-smithy-types", @@ -788,9 +799,9 @@ dependencies = [ [[package]] name = "aws-smithy-http-tower" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5856d2f1063c0f726a85f32dcd2a9f5a1d994eb27b156abccafc7260f3f471d" +checksum = "4f38231d3f5dac9ac7976f44e12803add1385119ffca9e5f050d8e980733d164" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -804,18 +815,33 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb33659b68480495b5f906b946c8642928440118b1d7e26a25a067303ca01a5" +checksum = "4bd83ff2b79e9f729746fcc8ad798676b68fe6ea72986571569a5306a277a182" dependencies = [ "aws-smithy-types", ] +[[package]] +name = "aws-smithy-protocol-test" +version = "0.54.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d1c9bcb35ce11055ec128dab2c66a7ed47e2dfff99883e32c21a1ab6d6bee6" +dependencies = [ + "assert-json-diff", + "http", + "pretty_assertions", + "regex", + "roxmltree", + "serde_json", + "thiserror", +] + [[package]] name = "aws-smithy-query" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c4b21ee0e30ff046e87c7b7e017b99d445b42a81fe52c6e5139b23b795a98ae" +checksum = "a2f0445dafe9d2cd50b44339ae3c3ed46549aad8ac696c52ad660b3e7ae8682b" dependencies = [ "aws-smithy-types", "urlencoding", @@ -823,9 +849,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2013465a070decdeb3e85ceb3370ae85ba05f56f914abfd89858d7281c4f12c3" +checksum = "8161232eda10290f5136610a1eb9de56aceaccd70c963a26a260af20ac24794f" dependencies = [ "base64-simd", "itoa", @@ -836,18 +862,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.53.1" +version = "0.54.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d27bfaa164aa94aac721726a83aa78abe708a275e88a573e103b4961c5f0ede" +checksum = "343ffe9a9bb3f542675f4df0e0d5933513d6ad038ca3907ad1767ba690a99684" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "0.53.0" +version = "0.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61f00f4b0cdd345686e6389f3343a3020f93232d20040802b87673ddc2d02956" +checksum = "f8f15b34253b68cde08e39b0627cc6101bcca64351229484b4743392c035d057" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -1000,11 +1026,12 @@ checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64-simd" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "simd-abstraction", + "outref", + "vsimd", ] [[package]] @@ -1131,7 +1158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" dependencies = [ "async-channel 2.1.0", - "async-lock 3.1.0", + "async-lock 3.1.1", "async-task", "fastrand 2.0.1", "futures-io", @@ -1348,9 +1375,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bytes-utils" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ "bytes", "either", @@ -1404,8 +1431,8 @@ dependencies = [ "auto_ops", "ck-meow", "digest 0.10.7", - "ecdsa 0.16.8", - "elliptic-curve 0.13.6", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "event-listener 2.5.3", "k256", "magikitten", @@ -1452,9 +1479,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" +checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" dependencies = [ "serde", ] @@ -1793,9 +1820,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", @@ -2121,6 +2148,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.8.1" @@ -2210,16 +2243,16 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der 0.7.8", "digest 0.10.7", - "elliptic-curve 0.13.6", + "elliptic-curve 0.13.8", "rfc6979", "serdect", - "signature 2.1.0", + "signature 2.2.0", "spki 0.7.2", ] @@ -2282,12 +2315,12 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.13.6" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", - "crypto-bigint 0.5.4", + "crypto-bigint 0.5.5", "digest 0.10.7", "ff 0.13.0", "generic-array 0.14.7", @@ -2311,18 +2344,18 @@ dependencies = [ [[package]] name = "enum-map" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed40247825a1a0393b91b51d475ea1063a6cbbf0847592e7f13fb427aca6a716" +checksum = "09e6b4f374c071b18172e23134e01026653dc980636ee139e0dfe59c538c61e5" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7933cd46e720348d29ed1493f89df9792563f272f96d8f13d18afe03b32f8cb8" +checksum = "bfdb3d73d1beaf47c8593a1364e577fde072677cbfd103600345c0f547408cc0" dependencies = [ "proc-macro2", "quote", @@ -2363,9 +2396,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys 0.48.0", @@ -2398,6 +2431,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "extend" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47da3a72ec598d9c8937a7ebca8962a5c7a1f28444e38c2b33c771ba3f55f05" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -2650,7 +2695,11 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb" dependencies = [ + "fastrand 2.0.1", "futures-core", + "futures-io", + "memchr", + "parking", "pin-project-lite", ] @@ -2928,9 +2977,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -2938,7 +2987,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", "tokio-util 0.7.10", @@ -3147,7 +3196,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.8", + "rustls 0.21.9", "rustls-native-certs", "tokio", "tokio-rustls 0.24.1", @@ -3352,7 +3401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.3", - "rustix 0.38.23", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -3447,17 +3496,17 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" dependencies = [ "cfg-if 1.0.0", - "ecdsa 0.16.8", - "elliptic-curve 0.13.6", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "once_cell", "serdect", "sha2 0.10.8", - "signature 2.1.0", + "signature 2.2.0", ] [[package]] @@ -3756,8 +3805,12 @@ dependencies = [ "aes-gcm", "anyhow", "async-process", + "aws-config", + "aws-sdk-s3", + "aws-types", "backon", "bollard", + "cait-sith", "clap 4.4.8", "curv-kzen", "ed25519-dalek", @@ -3765,6 +3818,7 @@ dependencies = [ "futures", "hex 0.4.3", "hyper", + "k256", "mpc-contract", "mpc-recovery", "mpc-recovery-node", @@ -3772,6 +3826,8 @@ dependencies = [ "near-crypto 0.17.0", "near-fetch", "near-jsonrpc-client", + "near-lake-framework", + "near-lake-primitives", "near-primitives 0.17.0", "near-units", "near-workspaces", @@ -3798,6 +3854,9 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "aws-config", + "aws-sdk-s3", + "aws-types", "axum", "axum-extra", "cait-sith", @@ -4077,8 +4136,7 @@ dependencies = [ [[package]] name = "near-lake-context-derive" version = "0.8.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcc377f95e18b5aae830f673f6c885f5ce13994da7ba3235983cd409b6f7533" +source = "git+https://github.com/near/near-lake-framework-rs.git?branch=daniyar/reproduce#6b09538e8e84e12c38d99fa02d9f8bfd27b18af8" dependencies = [ "quote", "syn 2.0.39", @@ -4087,8 +4145,7 @@ dependencies = [ [[package]] name = "near-lake-framework" version = "0.8.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea2651abd516e1dc0797e8be2125442fef5e3778e88ea5dd4820c69c1dee7f7" +source = "git+https://github.com/near/near-lake-framework-rs.git?branch=daniyar/reproduce#6b09538e8e84e12c38d99fa02d9f8bfd27b18af8" dependencies = [ "async-stream", "async-trait", @@ -4111,8 +4168,7 @@ dependencies = [ [[package]] name = "near-lake-primitives" version = "0.8.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa5dbc9e45dfb9b06cc6c8ab7112fb9fdb3b8c776cd15d0e24148b9be333446" +source = "git+https://github.com/near/near-lake-framework-rs.git?branch=daniyar/reproduce#6b09538e8e84e12c38d99fa02d9f8bfd27b18af8" dependencies = [ "anyhow", "near-crypto 0.17.0", @@ -4358,9 +4414,9 @@ checksum = "6540152fba5e96fe5d575b79e8cd244cf2add747bb01362426bdc069bc3a23bc" [[package]] name = "near-sys" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e307313276eaeced2ca95740b5639e1f3125b7c97f0a1151809d105f1aa8c6d3" +checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b" [[package]] name = "near-units" @@ -4916,9 +4972,9 @@ checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "outref" -version = "0.1.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] name = "overload" @@ -5230,7 +5286,7 @@ dependencies = [ "cfg-if 1.0.0", "concurrent-queue", "pin-project-lite", - "rustix 0.38.23", + "rustix 0.38.25", "tracing", "windows-sys 0.48.0", ] @@ -5265,6 +5321,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "primitive-types" version = "0.10.1" @@ -5804,7 +5870,7 @@ dependencies = [ "once_cell", "percent-encoding 2.3.0", "pin-project-lite", - "rustls 0.21.8", + "rustls 0.21.9", "rustls-pemfile", "serde", "serde_json", @@ -5893,6 +5959,15 @@ dependencies = [ "serde", ] +[[package]] +name = "roxmltree" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" +dependencies = [ + "xmlparser", +] + [[package]] name = "rsa" version = "0.8.2" @@ -5908,7 +5983,7 @@ dependencies = [ "pkcs1", "pkcs8 0.9.0", "rand_core 0.6.4", - "signature 2.1.0", + "signature 2.2.0", "subtle", "zeroize", ] @@ -5975,9 +6050,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.23" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb93593068e9babdad10e4fce47dc9b3ac25315a72a59766ffd9e9a71996a04" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", @@ -6000,9 +6075,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.8" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", "ring 0.17.5", @@ -6514,23 +6589,14 @@ dependencies = [ [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", ] -[[package]] -name = "simd-abstraction" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" -dependencies = [ - "outref", -] - [[package]] name = "simple_asn1" version = "0.6.2" @@ -6865,7 +6931,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.23", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -7079,7 +7145,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.8", + "rustls 0.21.9", "tokio", ] @@ -7433,9 +7499,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry-instrumentation-sdk" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752ddd669b14a08036a89045e4ac4497ff7ce254e11386647751f36d7c7849ea" +checksum = "f523eba1b52bb854b804d43a039aafeaee5a623015065adbfef8016825319c15" dependencies = [ "http", "opentelemetry-http", @@ -7579,7 +7645,7 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.21.8", + "rustls 0.21.9", "rustls-webpki", "url 2.4.1", "webpki-roots", @@ -7650,6 +7716,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "waker-fn" version = "1.1.1" @@ -7796,7 +7868,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.23", + "rustix 0.38.25", ] [[package]] @@ -8026,6 +8098,12 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "yup-oauth2" version = "8.3.0" @@ -8042,7 +8120,7 @@ dependencies = [ "itertools 0.10.5", "log", "percent-encoding 2.3.0", - "rustls 0.21.8", + "rustls 0.21.9", "rustls-pemfile", "seahash", "serde", @@ -8075,9 +8153,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] diff --git a/contract/src/lib.rs b/contract/src/lib.rs index a42c379d4..78039848f 100644 --- a/contract/src/lib.rs +++ b/contract/src/lib.rs @@ -1,7 +1,7 @@ use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::serde::{Deserialize, Serialize}; use near_sdk::{env, near_bindgen, AccountId, PanicOnDefault, PublicKey}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; type ParticipantId = u32; @@ -26,28 +26,28 @@ pub struct ParticipantInfo { #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug)] pub struct InitializingContractState { - pub participants: HashMap, + pub participants: BTreeMap, pub threshold: usize, - pub pk_votes: HashMap>, + pub pk_votes: BTreeMap>, } #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug)] pub struct RunningContractState { pub epoch: u64, - pub participants: HashMap, + pub participants: BTreeMap, pub threshold: usize, pub public_key: PublicKey, - pub candidates: HashMap, - pub join_votes: HashMap>, - pub leave_votes: HashMap>, + pub candidates: BTreeMap, + pub join_votes: BTreeMap>, + pub leave_votes: BTreeMap>, } #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Debug)] pub struct ResharingContractState { pub old_epoch: u64, - pub old_participants: HashMap, + pub old_participants: BTreeMap, // TODO: only store diff to save on storage - pub new_participants: HashMap, + pub new_participants: BTreeMap, pub threshold: usize, pub public_key: PublicKey, pub finished_votes: HashSet, @@ -69,12 +69,12 @@ pub struct MpcContract { #[near_bindgen] impl MpcContract { #[init] - pub fn init(threshold: usize, participants: HashMap) -> Self { + pub fn init(threshold: usize, participants: BTreeMap) -> Self { MpcContract { protocol_state: ProtocolContractState::Initializing(InitializingContractState { participants, threshold, - pk_votes: HashMap::new(), + pk_votes: BTreeMap::new(), }), } } @@ -211,9 +211,9 @@ impl MpcContract { participants: participants.clone(), threshold: *threshold, public_key, - candidates: HashMap::new(), - join_votes: HashMap::new(), - leave_votes: HashMap::new(), + candidates: BTreeMap::new(), + join_votes: BTreeMap::new(), + leave_votes: BTreeMap::new(), }); true } else { @@ -251,9 +251,9 @@ impl MpcContract { participants: new_participants.clone(), threshold: *threshold, public_key: public_key.clone(), - candidates: HashMap::new(), - join_votes: HashMap::new(), - leave_votes: HashMap::new(), + candidates: BTreeMap::new(), + join_votes: BTreeMap::new(), + leave_votes: BTreeMap::new(), }); true } else { @@ -270,4 +270,12 @@ impl MpcContract { _ => env::panic_str("protocol is not resharing right now"), } } + + #[allow(unused_variables)] + pub fn sign(&mut self, payload: [u8; 32]) -> [u8; 32] { + near_sdk::env::random_seed_array() + } + + #[allow(unused_variables)] + pub fn respond(&mut self, receipt_id: [u8; 32], big_r: String, s: String) {} } diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index c1e1de55c..de7c1d45b 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -8,23 +8,22 @@ publish = false aes-gcm = "0.10" anyhow = { version = "1.0", features = ["backtrace"] } async-process = "1" +aws-config = "0.54.0" +aws-sdk-s3 = "0.24.0" +aws-types = "0.54.0" bollard = "0.13" +cait-sith = { git = "https://github.com/LIT-Protocol/cait-sith.git", features = [ + "k256", +] } clap = { version = "4.2", features = ["derive", "env"] } curv = { package = "curv-kzen", version = "0.9", default-features = false } ed25519-dalek = { version = "1.0.1", features = ["serde"] } futures = "0.3" hex = "0.4.3" hyper = { version = "0.14", features = ["full"] } -mpc-contract = { path = "../contract" } -mpc-recovery = { path = "../mpc-recovery" } -mpc-recovery-node = { path = "../node" } +k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"] } multi-party-eddsa = { git = "https://github.com/DavidM-D/multi-party-eddsa.git", rev = "25ae4fdc5ff7819ae70e73ab4afacf1c24fc4da1" } tracing = "0.1" -near-crypto = "0.17" -near-fetch = "0.0.12" -near-jsonrpc-client = "0.6" -near-primitives = "0.17" -near-units = "0.2.0" nix = { version = "0.27", features = ["signal"] } once_cell = "1" rand = "0.7" @@ -36,6 +35,18 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } near-workspaces = "0.8.0" toml = "0.8.1" +near-crypto = "0.17" +near-fetch = "0.0.12" +near-jsonrpc-client = "0.6" +near-primitives = "0.17" +near-lake-framework = { git = "https://github.com/near/near-lake-framework-rs.git", branch = "daniyar/reproduce" } +near-lake-primitives = { git = "https://github.com/near/near-lake-framework-rs.git", branch = "daniyar/reproduce" } +near-units = "0.2.0" + +mpc-contract = { path = "../contract" } +mpc-recovery = { path = "../mpc-recovery" } +mpc-recovery-node = { path = "../node" } + [dev-dependencies] backon = "0.4" rand = "0.7" diff --git a/integration-tests/src/env/containers.rs b/integration-tests/src/env/containers.rs index 54b8d5ac2..4b23453a2 100644 --- a/integration-tests/src/env/containers.rs +++ b/integration-tests/src/env/containers.rs @@ -517,8 +517,7 @@ impl<'a> LocalStack<'a> { s3_region: String, ) -> anyhow::Result> { tracing::info!("running LocalStack container..."); - let image = GenericImage::new("localstack/localstack", "latest") - .with_exposed_port(Self::S3_CONTAINER_PORT) + let image = GenericImage::new("localstack/localstack", "3.0.0") .with_wait_for(WaitFor::message_on_stdout("Running on")); let image: RunnableImage = image.into(); let image = image.with_network(network); @@ -554,8 +553,8 @@ impl<'a> LocalStack<'a> { .await?; let s3_address = format!("http://{}:{}", address, Self::S3_CONTAINER_PORT); - let s3_host_port = container.get_host_port_ipv4(Self::S3_CONTAINER_PORT); - let s3_host_address = format!("http://127.0.0.1:{s3_host_port}"); + let s3_host_port = container.get_host_port_ipv6(Self::S3_CONTAINER_PORT); + let s3_host_address = format!("http://[::1]:{s3_host_port}"); tracing::info!( s3_address, diff --git a/integration-tests/src/indexer.rs b/integration-tests/src/indexer.rs new file mode 100644 index 000000000..ad310c007 --- /dev/null +++ b/integration-tests/src/indexer.rs @@ -0,0 +1,100 @@ +use k256::{AffinePoint, Scalar}; +use near_lake_framework::{LakeBuilder, LakeContext}; +use near_lake_primitives::actions::ActionMetaDataExt; +use near_lake_primitives::{receipts::ExecutionStatus, AccountId}; +use near_primitives::hash::CryptoHash; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +#[derive(Debug, Serialize, Deserialize)] +struct RespondPayload { + receipt_id: [u8; 32], + big_r: AffinePoint, + s: Scalar, +} + +pub struct FullSignature { + pub big_r: AffinePoint, + pub s: Scalar, +} + +#[derive(LakeContext)] +struct Context { + mpc_contract_id: AccountId, + responses: Arc>>, +} + +async fn handle_block( + mut block: near_lake_primitives::block::Block, + ctx: &Context, +) -> anyhow::Result<()> { + for action in block.actions().cloned().collect::>() { + if action.receiver_id() == ctx.mpc_contract_id { + let receipt = block.receipt_by_id(&action.receipt_id()).unwrap(); + if let Some(function_call) = action.as_function_call() { + if function_call.method_name() == "respond" { + let ExecutionStatus::SuccessValue(_) = receipt.status() else { + tracing::error!("indexed a failed `respond` function call"); + continue; + }; + if let Ok(respond_payload) = + serde_json::from_slice::<'_, RespondPayload>(function_call.args()) + { + let receipt_id = CryptoHash(respond_payload.receipt_id); + tracing::info!( + receipt_id = %receipt_id, + caller_id = receipt.predecessor_id().to_string(), + big_r = ?respond_payload.big_r, + s = ?respond_payload.s, + "indexed new `respond` function call" + ); + let mut responses = ctx.responses.write().await; + responses.insert( + receipt_id, + FullSignature { + big_r: respond_payload.big_r, + s: respond_payload.s, + }, + ); + drop(responses); + } + } + } + } + } + Ok(()) +} + +pub fn run( + s3_bucket: &str, + s3_region: &str, + start_block_height: u64, + s3_url: &str, + mpc_contract_id: AccountId, + responses: Arc>>, +) -> anyhow::Result<()> { + let mut lake_builder = LakeBuilder::default() + .s3_bucket_name(s3_bucket) + .s3_region_name(s3_region) + .start_block_height(start_block_height); + let lake = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + let aws_config = aws_config::from_env().load().await; + let s3_config = aws_sdk_s3::config::Builder::from(&aws_config) + .endpoint_url(s3_url) + .build(); + lake_builder = lake_builder.s3_config(s3_config); + lake_builder.build() + })?; + let context = Context { + mpc_contract_id, + responses, + }; + lake.run_with_context(handle_block, &context)?; + Ok(()) +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index dcd12bc4f..ee0bc5f73 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -12,6 +12,7 @@ use crate::env::containers::{self, LocalStack}; use testcontainers::{Container, GenericImage}; pub mod env; +pub mod indexer; pub mod mpc; pub mod multichain; pub mod sandbox; diff --git a/integration-tests/src/multichain/containers.rs b/integration-tests/src/multichain/containers.rs index f579533a0..f0f2794a8 100644 --- a/integration-tests/src/multichain/containers.rs +++ b/integration-tests/src/multichain/containers.rs @@ -41,8 +41,9 @@ impl<'a> Node<'a> { account_sk: account_sk.to_string().parse()?, web_port: Self::CONTAINER_PORT, indexer_options: mpc_recovery_node::indexer::Options { - s3_bucket: ctx.localstack.s3_host_address.clone(), + s3_bucket: ctx.localstack.s3_bucket.clone(), s3_region: ctx.localstack.s3_region.clone(), + s3_url: Some(ctx.localstack.s3_host_address.clone()), start_block_height: 0, }, } diff --git a/integration-tests/src/multichain/local.rs b/integration-tests/src/multichain/local.rs index da32abd3f..807b72057 100644 --- a/integration-tests/src/multichain/local.rs +++ b/integration-tests/src/multichain/local.rs @@ -30,8 +30,9 @@ impl Node { account_sk: account_sk.to_string().parse()?, web_port, indexer_options: mpc_recovery_node::indexer::Options { - s3_bucket: ctx.localstack.s3_host_address.clone(), + s3_bucket: ctx.localstack.s3_bucket.clone(), s3_region: ctx.localstack.s3_region.clone(), + s3_url: Some(ctx.localstack.s3_host_address.clone()), start_block_height: 0, }, }; diff --git a/integration-tests/tests/lib.rs b/integration-tests/tests/lib.rs index 8ae6381ed..d2ebc9ccc 100644 --- a/integration-tests/tests/lib.rs +++ b/integration-tests/tests/lib.rs @@ -10,9 +10,15 @@ use mpc_recovery::{ ClaimOidcResponse, MpcPkResponse, NewAccountResponse, SignResponse, UserCredentialsResponse, }, }; -use mpc_recovery_integration_tests::env; use mpc_recovery_integration_tests::env::containers::DockerClient; +use mpc_recovery_integration_tests::indexer::FullSignature; +use mpc_recovery_integration_tests::{env, indexer}; +use near_primitives::hash::CryptoHash; use near_workspaces::{network::Sandbox, Worker}; +use std::collections::HashMap; +use std::sync::Arc; +use std::thread; +use tokio::sync::RwLock; pub struct TestContext { env: String, @@ -63,6 +69,7 @@ pub struct MultichainTestContext<'a> { nodes: mpc_recovery_integration_tests::multichain::Nodes<'a>, rpc_client: near_fetch::Client, http_client: reqwest::Client, + responses: Arc>>, } async fn with_multichain_nodes(nodes: usize, f: F) -> anyhow::Result<()> @@ -72,11 +79,30 @@ where let docker_client = DockerClient::default(); let nodes = mpc_recovery_integration_tests::multichain::run(nodes, &docker_client).await?; + let s3_bucket = nodes.ctx().localstack.s3_bucket.clone(); + let s3_region = nodes.ctx().localstack.s3_region.clone(); + let s3_url = nodes.ctx().localstack.s3_host_address.clone(); + let mpc_contract_id = nodes.ctx().mpc_contract.id().clone(); + let responses = Arc::new(RwLock::new(HashMap::new())); + let responses_clone = responses.clone(); + thread::spawn(move || { + indexer::run( + &s3_bucket, + &s3_region, + 0, + &s3_url, + mpc_contract_id, + responses_clone, + ) + .unwrap(); + }); + let rpc_client = near_fetch::Client::new(&nodes.ctx().lake_indexer.rpc_host_address); f(MultichainTestContext { nodes, rpc_client, http_client: reqwest::Client::default(), + responses, }) .await?; @@ -190,7 +216,9 @@ mod wait_for { use backon::Retryable; use mpc_contract::ProtocolContractState; use mpc_contract::RunningContractState; + use mpc_recovery_integration_tests::indexer::FullSignature; use mpc_recovery_node::web::StateView; + use near_primitives::hash::CryptoHash; pub async fn running_mpc<'a>( ctx: &MultichainTestContext<'a>, @@ -272,6 +300,23 @@ mod wait_for { .retry(&ExponentialBuilder::default().with_max_times(6)) .await } + + pub async fn has_response<'a>( + ctx: &MultichainTestContext<'a>, + receipt_id: CryptoHash, + ) -> anyhow::Result { + let is_enough_presignatures = || async { + let mut responses = ctx.responses.write().await; + if let Some(signature) = responses.remove(&receipt_id) { + return Ok(signature); + } + drop(responses); + anyhow::bail!("mpc has not responded yet") + }; + is_enough_presignatures + .retry(&ExponentialBuilder::default().with_max_times(8)) + .await + } } trait MpcCheck { diff --git a/integration-tests/tests/multichain/mod.rs b/integration-tests/tests/multichain/mod.rs index e7fd71c47..eb85a0aea 100644 --- a/integration-tests/tests/multichain/mod.rs +++ b/integration-tests/tests/multichain/mod.rs @@ -1,4 +1,11 @@ use crate::{wait_for, with_multichain_nodes}; +use k256::elliptic_curve::scalar::FromUintUnchecked; +use k256::elliptic_curve::sec1::FromEncodedPoint; +use k256::{AffinePoint, EncodedPoint, Scalar, Secp256k1, U256}; +use near_crypto::InMemorySigner; +use near_primitives::transaction::{Action, FunctionCallAction}; +use near_primitives::views::ExecutionStatusView; +use rand::Rng; use test_log::test; #[test(tokio::test)] @@ -49,3 +56,65 @@ async fn test_triples_and_presignatures() -> anyhow::Result<()> { }) .await } + +#[test(tokio::test)] +async fn test_signature() -> anyhow::Result<()> { + with_multichain_nodes(3, |ctx| { + Box::pin(async move { + // Wait for network to complete key generation + let state_0 = wait_for::running_mpc(&ctx, 0).await?; + assert_eq!(state_0.participants.len(), 3); + + let worker = &ctx.nodes.ctx().worker; + let (account_id, secret_key) = worker.dev_generate().await; + worker + .create_tla(account_id.clone(), secret_key.clone()) + .await? + .into_result()?; + let payload: [u8; 32] = rand::thread_rng().gen(); + let outcome = ctx + .rpc_client + .send_tx( + &InMemorySigner { + account_id, + public_key: secret_key.public_key().clone().into(), + secret_key: secret_key.to_string().parse()?, + }, + ctx.nodes.ctx().mpc_contract.id(), + vec![Action::FunctionCall(FunctionCallAction { + method_name: "sign".to_string(), + args: serde_json::to_vec(&serde_json::json!({ + "payload": payload + }))?, + gas: 300_000_000_000_000, + deposit: 0, + })], + ) + .await?; + let ExecutionStatusView::SuccessReceiptId(receipt_id) = + outcome.transaction_outcome.outcome.status + else { + anyhow::bail!("missing receipt id"); + }; + + let signature = wait_for::has_response(&ctx, receipt_id).await?; + let signature_output = cait_sith::FullSignature:: { + big_r: signature.big_r, + s: signature.s, + }; + + let mut bytes = vec![0x04]; + bytes.extend_from_slice(&state_0.public_key.as_bytes()[1..]); + let point = EncodedPoint::from_bytes(bytes).unwrap(); + let public_key = AffinePoint::from_encoded_point(&point).unwrap(); + + assert!(signature_output.verify( + &public_key, + &Scalar::from_uint_unchecked(U256::from_le_slice(&payload)), + )); + + Ok(()) + }) + }) + .await +} diff --git a/node/Cargo.toml b/node/Cargo.toml index 6b28242fa..e42c0f2fc 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -10,6 +10,9 @@ path = "src/main.rs" [dependencies] anyhow = { version = "1", features = ["backtrace"] } async-trait = "0.1" +aws-config = "0.54.0" +aws-sdk-s3 = "0.24.0" +aws-types = "0.54.0" axum = { version = "0.6.19" } axum-extra = "0.7" cait-sith = { git = "https://github.com/LIT-Protocol/cait-sith.git", features = [ @@ -32,8 +35,8 @@ url = { version = "2.4.0", features = ["serde"] } near-crypto = "0.17" near-fetch = "0.0.12" -near-lake-framework = "0.8.0-beta.2" -near-lake-primitives = "0.8.0-beta.2" +near-lake-framework = { git = "https://github.com/near/near-lake-framework-rs.git", branch = "daniyar/reproduce" } +near-lake-primitives = { git = "https://github.com/near/near-lake-framework-rs.git", branch = "daniyar/reproduce" } near-primitives = "0.17" near-sdk = "4.1.1" diff --git a/node/src/cli.rs b/node/src/cli.rs index 005ac4d63..053faedf2 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -1,11 +1,13 @@ -use crate::protocol::MpcSignProtocol; +use crate::protocol::{MpcSignProtocol, SignQueue}; use crate::{indexer, web}; use cait_sith::protocol::Participant; use clap::Parser; use local_ip_address::local_ip; use near_crypto::{InMemorySigner, SecretKey}; use near_primitives::types::AccountId; -use tokio::sync::mpsc; +use std::sync::Arc; +use std::thread; +use tokio::sync::{mpsc, RwLock}; use tracing_subscriber::EnvFilter; use url::Url; @@ -102,6 +104,13 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { account_sk, indexer_options, } => { + let sign_queue = Arc::new(RwLock::new(SignQueue::new())); + let a = indexer_options.clone(); + let b = mpc_contract_id.clone(); + let c = sign_queue.clone(); + let indexer_handler = thread::spawn(move || { + indexer::run(a, b, c).unwrap(); + }); tokio::runtime::Builder::new_multi_thread() .enable_all() .build() @@ -122,6 +131,7 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { rpc_client.clone(), signer.clone(), receiver, + sign_queue.clone(), ); tracing::debug!("protocol initialized"); let protocol_handle = tokio::spawn(async move { @@ -149,7 +159,7 @@ pub fn run(cmd: Cli) -> anyhow::Result<()> { anyhow::Ok(()) })?; - indexer::run(&indexer_options, mpc_contract_id)?; + indexer_handler.join().unwrap(); } } diff --git a/node/src/indexer.rs b/node/src/indexer.rs index 8e7f498f8..2c79b9053 100644 --- a/node/src/indexer.rs +++ b/node/src/indexer.rs @@ -1,9 +1,13 @@ +use crate::protocol::{SignQueue, SignRequest}; use near_lake_framework::{LakeBuilder, LakeContext}; +use near_lake_primitives::actions::ActionMetaDataExt; use near_lake_primitives::{receipts::ExecutionStatus, AccountId}; use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::sync::RwLock; /// Configures exporter of span and trace data. -#[derive(Debug, clap::Parser)] +#[derive(Debug, Clone, clap::Parser)] pub struct Options { /// AWS S3 bucket name for NEAR Lake Indexer #[clap( @@ -21,6 +25,10 @@ pub struct Options { )] pub s3_region: String, + /// AWS S3 URL for NEAR Lake Indexer (can be used to point to LocalStack) + #[clap(long, env("MPC_RECOVERY_INDEXER_S3_URL"))] + pub s3_url: Option, + /// The block height to start indexing from. // Defaults to the latest block on 2023-11-14 07:40:22 AM UTC #[clap(long, default_value = "145964826")] @@ -29,50 +37,66 @@ pub struct Options { impl Options { pub fn into_str_args(self) -> Vec { - vec![ + let mut opts = vec![ "--s3-bucket".to_string(), self.s3_bucket, "--s3-region".to_string(), self.s3_region, "--start-block-height".to_string(), self.start_block_height.to_string(), - ] + ]; + + if let Some(s3_url) = self.s3_url { + opts.extend(vec!["--s3-url".to_string(), s3_url]); + } + + opts } } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] struct SignPayload { - payload: Vec, + payload: [u8; 32], } #[derive(LakeContext)] struct Context { - signer_account: AccountId, + mpc_contract_id: AccountId, + queue: Arc>, } async fn handle_block( mut block: near_lake_primitives::block::Block, ctx: &Context, ) -> anyhow::Result<()> { - for tx in block.transactions() { - if tx.receiver_id() == &ctx.signer_account - && matches!( - tx.status(), - ExecutionStatus::SuccessValue(_) | ExecutionStatus::SuccessReceiptId(_) - ) - { - for action in tx.actions_included() { - if let Some(function_call) = action.as_function_call() { - if function_call.method_name() == "sign" { - if let Ok(sign_payload) = - serde_json::from_slice::<'_, SignPayload>(function_call.args()) - { - tracing::info!( - signer_id = tx.signer_id().to_string(), - bytes = sign_payload.payload.len(), - "new sign event" - ) - } + for action in block.actions().cloned().collect::>() { + if action.receiver_id() == ctx.mpc_contract_id { + let receipt = block.receipt_by_id(&action.receipt_id()).unwrap(); + let ExecutionStatus::SuccessValue(result) = receipt.status() else { + continue; + }; + if let Some(function_call) = action.as_function_call() { + if function_call.method_name() == "sign" { + if let Ok(sign_payload) = + serde_json::from_slice::<'_, SignPayload>(function_call.args()) + { + let Ok(entropy) = serde_json::from_slice::<'_, [u8; 32]>(&result) else { + continue; + }; + tracing::info!( + receipt_id = %receipt.receipt_id(), + caller_id = receipt.predecessor_id().to_string(), + payload = hex::encode(sign_payload.payload), + entropy = hex::encode(entropy), + "indexed new `sign` function call" + ); + let mut queue = ctx.queue.write().await; + queue.add(SignRequest { + receipt_id: receipt.receipt_id(), + msg_hash: sign_payload.payload, + entropy, + }); + drop(queue); } } } @@ -81,13 +105,41 @@ async fn handle_block( Ok(()) } -pub fn run(options: &Options, signer_account: AccountId) -> anyhow::Result<()> { - let lake = LakeBuilder::default() - .s3_bucket_name(&options.s3_bucket) - .s3_region_name(&options.s3_region) - .start_block_height(options.start_block_height) - .build()?; - let context = Context { signer_account }; +pub fn run( + options: Options, + mpc_contract_id: AccountId, + queue: Arc>, +) -> anyhow::Result<()> { + tracing::info!( + s3_bucket = options.s3_bucket, + s3_region = options.s3_region, + s3_url = options.s3_url, + start_block_height = options.start_block_height, + %mpc_contract_id, + "starting indexer" + ); + let mut lake_builder = LakeBuilder::default() + .s3_bucket_name(options.s3_bucket) + .s3_region_name(options.s3_region) + .start_block_height(options.start_block_height); + let lake = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + if let Some(s3_url) = options.s3_url { + let aws_config = aws_config::from_env().load().await; + let s3_config = aws_sdk_s3::config::Builder::from(&aws_config) + .endpoint_url(s3_url) + .build(); + lake_builder = lake_builder.s3_config(s3_config); + } + lake_builder.build() + })?; + let context = Context { + mpc_contract_id, + queue, + }; lake.run_with_context(handle_block, &context)?; Ok(()) } diff --git a/node/src/protocol/consensus.rs b/node/src/protocol/consensus.rs index ad8372983..9729b7132 100644 --- a/node/src/protocol/consensus.rs +++ b/node/src/protocol/consensus.rs @@ -3,7 +3,9 @@ use super::state::{ JoiningState, NodeState, PersistentNodeData, RunningState, StartedState, WaitingForConsensusState, }; +use super::SignQueue; use crate::protocol::presignature::PresignatureManager; +use crate::protocol::signature::SignatureManager; use crate::protocol::state::{GeneratingState, ResharingState}; use crate::protocol::triple::TripleManager; use crate::types::PrivateKeyShare; @@ -16,6 +18,8 @@ use near_crypto::InMemorySigner; use near_primitives::transaction::{Action, FunctionCallAction}; use near_primitives::types::AccountId; use std::cmp::Ordering; +use std::sync::Arc; +use tokio::sync::RwLock; use url::Url; pub trait ConsensusCtx { @@ -25,6 +29,7 @@ pub trait ConsensusCtx { fn signer(&self) -> &InMemorySigner; fn mpc_contract_id(&self) -> &AccountId; fn my_address(&self) -> &Url; + fn sign_queue(&self) -> Arc>; } #[derive(thiserror::Error, Debug)] @@ -95,6 +100,7 @@ impl ConsensusProtocol for StartedState { threshold: contract_state.threshold, private_share, public_key, + sign_queue: ctx.sign_queue(), triple_manager: TripleManager::new( participants_vec.clone(), ctx.me(), @@ -102,11 +108,17 @@ impl ConsensusProtocol for StartedState { epoch, ), presignature_manager: PresignatureManager::new( - participants_vec, + participants_vec.clone(), ctx.me(), contract_state.threshold, epoch, ), + signature_manager: SignatureManager::new( + participants_vec, + ctx.me(), + contract_state.public_key, + epoch, + ), })) } else { Ok(NodeState::Joining(JoiningState { public_key })) @@ -276,6 +288,7 @@ impl ConsensusProtocol for WaitingForConsensusState { threshold: self.threshold, private_share: self.private_share, public_key: self.public_key, + sign_queue: ctx.sign_queue(), triple_manager: TripleManager::new( participants_vec.clone(), ctx.me(), @@ -283,11 +296,17 @@ impl ConsensusProtocol for WaitingForConsensusState { self.epoch, ), presignature_manager: PresignatureManager::new( - participants_vec, + participants_vec.clone(), ctx.me(), self.threshold, self.epoch, ), + signature_manager: SignatureManager::new( + participants_vec, + ctx.me(), + self.public_key, + self.epoch, + ), })) } }, diff --git a/node/src/protocol/contract.rs b/node/src/protocol/contract.rs index e6075dde6..84592902d 100644 --- a/node/src/protocol/contract.rs +++ b/node/src/protocol/contract.rs @@ -4,14 +4,14 @@ use cait_sith::protocol::Participant; use mpc_contract::{ParticipantInfo, ProtocolContractState}; use near_sdk::AccountId; use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use url::Url; #[derive(Serialize, Deserialize, Debug)] pub struct InitializingContractState { - pub participants: HashMap, + pub participants: BTreeMap, pub threshold: usize, - pub pk_votes: HashMap>, + pub pk_votes: BTreeMap>, } impl From for InitializingContractState { @@ -41,12 +41,12 @@ impl From for InitializingContractState #[derive(Serialize, Deserialize, Debug)] pub struct RunningContractState { pub epoch: u64, - pub participants: HashMap, + pub participants: BTreeMap, pub threshold: usize, pub public_key: PublicKey, - pub candidates: HashMap, - pub join_votes: HashMap>, - pub leave_votes: HashMap>, + pub candidates: BTreeMap, + pub join_votes: BTreeMap>, + pub leave_votes: BTreeMap>, } impl From for RunningContractState { @@ -88,8 +88,8 @@ impl From for RunningContractState { #[derive(Serialize, Deserialize, Debug)] pub struct ResharingContractState { pub old_epoch: u64, - pub old_participants: HashMap, - pub new_participants: HashMap, + pub old_participants: BTreeMap, + pub new_participants: BTreeMap, pub threshold: usize, pub public_key: PublicKey, pub finished_votes: HashSet, @@ -120,7 +120,7 @@ pub enum ProtocolState { } impl ProtocolState { - pub fn participants(&self) -> &HashMap { + pub fn participants(&self) -> &BTreeMap { match self { ProtocolState::Initializing(InitializingContractState { participants, .. }) => { participants @@ -164,8 +164,8 @@ impl TryFrom for ProtocolState { } fn contract_participants_into_cait_participants( - participants: HashMap, -) -> HashMap { + participants: BTreeMap, +) -> BTreeMap { participants .into_values() .map(|p| { diff --git a/node/src/protocol/cryptography.rs b/node/src/protocol/cryptography.rs index 0aed2fd36..2d8f929fc 100644 --- a/node/src/protocol/cryptography.rs +++ b/node/src/protocol/cryptography.rs @@ -6,10 +6,15 @@ use crate::protocol::MpcMessage; use async_trait::async_trait; use cait_sith::protocol::{Action, InitializationError, Participant, ProtocolError}; use k256::elliptic_curve::group::GroupEncoding; +use near_crypto::InMemorySigner; +use near_primitives::types::AccountId; pub trait CryptographicCtx { fn me(&self) -> Participant; fn http_client(&self) -> &reqwest::Client; + fn rpc_client(&self) -> &near_fetch::Client; + fn signer(&self) -> &InMemorySigner; + fn mpc_contract_id(&self) -> &AccountId; } #[derive(thiserror::Error, Debug)] @@ -18,6 +23,8 @@ pub enum CryptographicError { SendError(#[from] SendError), #[error("unknown participant: {0:?}")] UnknownParticipant(Participant), + #[error("rpc error: {0}")] + RpcError(#[from] near_fetch::Error), #[error("cait-sith initialization error: {0}")] CaitSithInitializationError(#[from] InitializationError), #[error("cait-sith protocol error: {0}")] @@ -206,6 +213,35 @@ impl CryptographicProtocol for RunningState { .await?; } + let mut sign_queue = self.sign_queue.write().await; + sign_queue.organize(&self, ctx.me()); + let my_requests = sign_queue.my_requests(ctx.me()); + while self.presignature_manager.my_len() > 0 { + let Some((receipt_id, _)) = my_requests.iter().next() else { + break; + }; + let Some(presignature) = self.presignature_manager.take_mine() else { + break; + }; + let receipt_id = *receipt_id; + let my_request = my_requests.remove(&receipt_id).unwrap(); + self.signature_manager.generate( + receipt_id, + presignature, + self.public_key, + my_request.msg_hash, + )?; + } + drop(sign_queue); + for (p, msg) in self.signature_manager.poke()? { + let url = self.participants.get(&p).unwrap(); + http_client::message(ctx.http_client(), url.clone(), MpcMessage::Signature(msg)) + .await?; + } + self.signature_manager + .publish(ctx.rpc_client(), ctx.signer(), ctx.mpc_contract_id()) + .await?; + Ok(NodeState::Running(self)) } } diff --git a/node/src/protocol/message.rs b/node/src/protocol/message.rs index 7c64e2f85..c5d7d530e 100644 --- a/node/src/protocol/message.rs +++ b/node/src/protocol/message.rs @@ -1,8 +1,11 @@ -use std::collections::{HashMap, VecDeque}; - +use super::presignature::{self, PresignatureId}; use super::state::{GeneratingState, NodeState, ResharingState, RunningState}; +use super::triple::TripleId; +use async_trait::async_trait; use cait_sith::protocol::{InitializationError, MessageData, Participant, ProtocolError}; +use near_primitives::hash::CryptoHash; use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, VecDeque}; pub trait MessageCtx { fn me(&self) -> Participant; @@ -32,8 +35,19 @@ pub struct TripleMessage { #[derive(Serialize, Deserialize, Debug)] pub struct PresignatureMessage { pub id: u64, - pub triple0: u64, - pub triple1: u64, + pub triple0: TripleId, + pub triple1: TripleId, + pub epoch: u64, + pub from: Participant, + pub data: MessageData, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SignatureMessage { + pub receipt_id: CryptoHash, + pub proposer: Participant, + pub presignature_id: PresignatureId, + pub msg_hash: [u8; 32], pub epoch: u64, pub from: Participant, pub data: MessageData, @@ -45,14 +59,16 @@ pub enum MpcMessage { Resharing(ResharingMessage), Triple(TripleMessage), Presignature(PresignatureMessage), + Signature(SignatureMessage), } #[derive(Default)] pub struct MpcMessageQueue { generating: VecDeque, resharing_bins: HashMap>, - triple_bins: HashMap>>, - presignature_bins: HashMap>>, + triple_bins: HashMap>>, + presignature_bins: HashMap>>, + signature_bins: HashMap>>, } impl MpcMessageQueue { @@ -78,6 +94,13 @@ impl MpcMessageQueue { .entry(message.id) .or_default() .push_back(message), + MpcMessage::Signature(message) => self + .signature_bins + .entry(message.epoch) + .or_default() + .entry(message.receipt_id) + .or_default() + .push_back(message), } } } @@ -90,16 +113,18 @@ pub enum MessageHandleError { CaitSithProtocolError(#[from] ProtocolError), } +#[async_trait] pub trait MessageHandler { - fn handle( + async fn handle( &mut self, ctx: C, queue: &mut MpcMessageQueue, ) -> Result<(), MessageHandleError>; } +#[async_trait] impl MessageHandler for GeneratingState { - fn handle( + async fn handle( &mut self, _ctx: C, queue: &mut MpcMessageQueue, @@ -112,8 +137,9 @@ impl MessageHandler for GeneratingState { } } +#[async_trait] impl MessageHandler for ResharingState { - fn handle( + async fn handle( &mut self, _ctx: C, queue: &mut MpcMessageQueue, @@ -127,8 +153,9 @@ impl MessageHandler for ResharingState { } } +#[async_trait] impl MessageHandler for RunningState { - fn handle( + async fn handle( &mut self, _ctx: C, queue: &mut MpcMessageQueue, @@ -141,33 +168,91 @@ impl MessageHandler for RunningState { } } for (id, queue) in queue.presignature_bins.entry(self.epoch).or_default() { + let mut leftover_messages = Vec::new(); while let Some(message) = queue.pop_front() { - if let Some(protocol) = self.presignature_manager.get_or_generate( + match self.presignature_manager.get_or_generate( *id, message.triple0, message.triple1, &mut self.triple_manager, &self.public_key, &self.private_share, + ) { + Ok(protocol) => protocol.message(message.from, message.data), + Err(presignature::GenerationError::AlreadyGenerated) => { + tracing::info!(id, "presignature already generated, nothing left to do") + } + Err(presignature::GenerationError::TripleIsMissing(_)) => { + // Store the message until we are ready to process it + leftover_messages.push(message) + } + Err(presignature::GenerationError::CaitSithInitializationError(error)) => { + return Err(error.into()) + } + } + } + if !leftover_messages.is_empty() { + tracing::warn!( + msg_count = leftover_messages.len(), + "unable to process messages, storing for future" + ); + queue.extend(leftover_messages); + } + } + for (receipt_id, queue) in queue.signature_bins.entry(self.epoch).or_default() { + let mut leftover_messages = Vec::new(); + while let Some(message) = queue.pop_front() { + tracing::info!( + presignature_id = message.presignature_id, + "new signature message" + ); + // if !self + // .sign_queue + // .read() + // .await + // .contains(message.proposer, receipt_id.clone()) + // { + // leftover_messages.push(message); + // continue; + // }; + // TODO: Validate that the message matches our sign_queue + match self.signature_manager.get_or_generate( + *receipt_id, + message.proposer, + message.presignature_id, + message.msg_hash, + &mut self.presignature_manager, )? { - protocol.message(message.from, message.data); + Some(protocol) => protocol.message(message.from, message.data), + None => { + // Store the message until we are ready to process it + leftover_messages.push(message) + } } } + if !leftover_messages.is_empty() { + tracing::warn!( + msg_count = leftover_messages.len(), + "unable to process messages, storing for future" + ); + queue.extend(leftover_messages); + } } Ok(()) } } +#[async_trait] impl MessageHandler for NodeState { - fn handle( + async fn handle( &mut self, ctx: C, queue: &mut MpcMessageQueue, ) -> Result<(), MessageHandleError> { match self { - NodeState::Generating(state) => state.handle(ctx, queue), - NodeState::Resharing(state) => state.handle(ctx, queue), - NodeState::Running(state) => state.handle(ctx, queue), + NodeState::Generating(state) => state.handle(ctx, queue).await, + NodeState::Resharing(state) => state.handle(ctx, queue).await, + NodeState::Running(state) => state.handle(ctx, queue).await, _ => { tracing::debug!("skipping message processing"); Ok(()) diff --git a/node/src/protocol/mod.rs b/node/src/protocol/mod.rs index aaccc1132..2c2511955 100644 --- a/node/src/protocol/mod.rs +++ b/node/src/protocol/mod.rs @@ -3,11 +3,14 @@ mod contract; mod cryptography; mod message; mod presignature; +mod signature; mod state; mod triple; pub use contract::ProtocolState; pub use message::MpcMessage; +pub use signature::SignQueue; +pub use signature::SignRequest; pub use state::NodeState; use self::consensus::ConsensusCtx; @@ -33,6 +36,7 @@ struct Ctx { signer: InMemorySigner, rpc_client: near_fetch::Client, http_client: reqwest::Client, + sign_queue: Arc>, } impl ConsensusCtx for &Ctx { @@ -59,6 +63,10 @@ impl ConsensusCtx for &Ctx { fn my_address(&self) -> &Url { &self.my_address } + + fn sign_queue(&self) -> Arc> { + self.sign_queue.clone() + } } impl CryptographicCtx for &Ctx { @@ -69,6 +77,18 @@ impl CryptographicCtx for &Ctx { fn http_client(&self) -> &reqwest::Client { &self.http_client } + + fn rpc_client(&self) -> &near_fetch::Client { + &self.rpc_client + } + + fn signer(&self) -> &InMemorySigner { + &self.signer + } + + fn mpc_contract_id(&self) -> &AccountId { + &self.mpc_contract_id + } } impl MessageCtx for &Ctx { @@ -91,6 +111,7 @@ impl MpcSignProtocol { rpc_client: near_fetch::Client, signer: InMemorySigner, receiver: mpsc::Receiver, + sign_queue: Arc>, ) -> (Self, Arc>) { let state = Arc::new(RwLock::new(NodeState::Starting)); let ctx = Ctx { @@ -100,6 +121,7 @@ impl MpcSignProtocol { signer, rpc_client, http_client: reqwest::Client::new(), + sign_queue, }; let protocol = MpcSignProtocol { ctx, @@ -149,7 +171,7 @@ impl MpcSignProtocol { let mut state = std::mem::take(&mut *state_guard); state = state.progress(&self.ctx).await?; state = state.advance(&self.ctx, contract_state).await?; - state.handle(&self.ctx, &mut queue)?; + state.handle(&self.ctx, &mut queue).await?; *state_guard = state; drop(state_guard); tokio::time::sleep(Duration::from_millis(1000)).await; diff --git a/node/src/protocol/presignature.rs b/node/src/protocol/presignature.rs index 9dde4bcff..6e66ac6c9 100644 --- a/node/src/protocol/presignature.rs +++ b/node/src/protocol/presignature.rs @@ -6,7 +6,7 @@ use cait_sith::protocol::{Action, InitializationError, Participant, ProtocolErro use cait_sith::{KeygenOutput, PresignArguments, PresignOutput}; use k256::Secp256k1; use std::collections::hash_map::Entry; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; /// Unique number used to identify a specific ongoing presignature generation protocol. /// Without `PresignatureId` it would be unclear where to route incoming cait-sith presignature @@ -24,6 +24,17 @@ pub struct PresignatureGenerator { pub protocol: PresignatureProtocol, pub triple0: TripleId, pub triple1: TripleId, + pub mine: bool, +} + +#[derive(Debug, thiserror::Error)] +pub enum GenerationError { + #[error("presignature already generated")] + AlreadyGenerated, + #[error("triple {0} is missing")] + TripleIsMissing(TripleId), + #[error("cait-sith initialization error: {0}")] + CaitSithInitializationError(#[from] InitializationError), } /// Abstracts how triples are generated by providing a way to request a new triple that will be @@ -33,6 +44,8 @@ pub struct PresignatureManager { presignatures: HashMap, /// Ongoing triple generation protocols. generators: HashMap, + /// List of presignature ids generation of which was initiated by the current node. + mine: VecDeque, participants: Vec, me: Participant, @@ -50,6 +63,7 @@ impl PresignatureManager { Self { presignatures: HashMap::new(), generators: HashMap::new(), + mine: VecDeque::new(), participants, me, threshold, @@ -62,12 +76,18 @@ impl PresignatureManager { self.presignatures.len() } + /// Returns the number of unspent presignatures assigned to this node. + pub fn my_len(&self) -> usize { + self.mine.len() + } + /// Returns the number of unspent presignatures we will have in the manager once /// all ongoing generation protocols complete. pub fn potential_len(&self) -> usize { self.presignatures.len() + self.generators.len() } + #[allow(clippy::too_many_arguments)] fn generate_internal( participants: &[Participant], me: Participant, @@ -76,6 +96,7 @@ impl PresignatureManager { triple1: Triple, public_key: &PublicKey, private_share: &PrivateKeyShare, + mine: bool, ) -> Result { let protocol = Box::new(cait_sith::presign( participants, @@ -94,6 +115,7 @@ impl PresignatureManager { protocol, triple0: triple0.id, triple1: triple1.id, + mine, }) } @@ -115,6 +137,7 @@ impl PresignatureManager { triple1, public_key, private_share, + true, )?; self.generators.insert(id, generator); Ok(()) @@ -134,22 +157,22 @@ impl PresignatureManager { triple_manager: &mut TripleManager, public_key: &PublicKey, private_share: &PrivateKeyShare, - ) -> Result, InitializationError> { + ) -> Result<&mut PresignatureProtocol, GenerationError> { if self.presignatures.contains_key(&id) { - Ok(None) + Err(GenerationError::AlreadyGenerated) } else { match self.generators.entry(id) { Entry::Vacant(entry) => { tracing::info!(id, "joining protocol to generate a new presignature"); - let Some(triple0) = triple_manager.take(triple0) else { - tracing::warn!(triple_id = triple0, "triple0 is missing, can't join"); - return Ok(None); - }; - let triple1 = match triple_manager.take(triple1) { - Some(triple1) => triple1, - None => { - tracing::warn!(triple_id = triple1, "triple1 is missing, can't join"); - return Ok(None); + let (triple0, triple1) = match triple_manager.take_two(triple0, triple1) { + Ok(result) => result, + Err(missing_triple_id) => { + tracing::warn!( + triple0, + triple1, + "one of the triples is missing, can't join" + ); + return Err(GenerationError::TripleIsMissing(missing_triple_id)); } }; let generator = Self::generate_internal( @@ -160,15 +183,26 @@ impl PresignatureManager { triple1, public_key, private_share, + false, )?; let generator = entry.insert(generator); - Ok(Some(&mut generator.protocol)) + Ok(&mut generator.protocol) } - Entry::Occupied(entry) => Ok(Some(&mut entry.into_mut().protocol)), + Entry::Occupied(entry) => Ok(&mut entry.into_mut().protocol), } } } + pub fn take_mine(&mut self) -> Option { + tracing::info!(mine = ?self.mine, "my presignatures"); + let my_presignature_id = self.mine.pop_front()?; + Some(self.presignatures.remove(&my_presignature_id).unwrap()) + } + + pub fn take(&mut self, id: PresignatureId) -> Option { + self.presignatures.remove(&id) + } + /// Pokes all of the ongoing generation protocols and returns a vector of /// messages to be sent to the respective participant. /// @@ -225,6 +259,10 @@ impl PresignatureManager { ); self.presignatures .insert(*id, Presignature { id: *id, output }); + if generator.mine { + tracing::info!(id, "assigning presignature to myself"); + self.mine.push_back(*id); + } // Do not retain the protocol return false; } diff --git a/node/src/protocol/signature.rs b/node/src/protocol/signature.rs new file mode 100644 index 000000000..f879575c2 --- /dev/null +++ b/node/src/protocol/signature.rs @@ -0,0 +1,311 @@ +use super::message::SignatureMessage; +use super::presignature::{Presignature, PresignatureId, PresignatureManager}; +use super::state::RunningState; +use crate::types::{PublicKey, SignatureProtocol}; +use crate::util::AffinePointExt; +use cait_sith::protocol::{Action, InitializationError, Participant, ProtocolError}; +use cait_sith::FullSignature; +use k256::elliptic_curve::scalar::FromUintUnchecked; +use k256::{Scalar, Secp256k1, U256}; +use near_crypto::Signer; +use near_fetch::signer::ExposeAccountId; +use near_primitives::hash::CryptoHash; +use near_primitives::transaction::FunctionCallAction; +use near_primitives::types::AccountId; +use rand::rngs::StdRng; +use rand::seq::{IteratorRandom, SliceRandom}; +use rand::SeedableRng; +use std::collections::hash_map::Entry; +use std::collections::HashMap; + +pub struct SignRequest { + pub receipt_id: CryptoHash, + pub msg_hash: [u8; 32], + pub entropy: [u8; 32], +} + +#[derive(Default)] +pub struct SignQueue { + unorganized_requests: Vec, + requests: HashMap>, +} + +impl SignQueue { + pub fn new() -> Self { + Self::default() + } + + pub fn add(&mut self, request: SignRequest) { + tracing::info!( + receipt_id = %request.receipt_id, + payload = hex::encode(request.msg_hash), + entropy = hex::encode(request.entropy), + "new sign request" + ); + self.unorganized_requests.push(request); + } + + pub fn organize(&mut self, state: &RunningState, me: Participant) { + for request in self.unorganized_requests.drain(..) { + let mut rng = StdRng::from_seed(request.entropy); + let subset = state + .participants + .keys() + .choose_multiple(&mut rng, state.threshold); + let proposer = **subset.choose(&mut rng).unwrap(); + if subset.contains(&&me) { + tracing::info!( + receipt_id = %request.receipt_id, + ?subset, + ?proposer, + "saving sign request: node is in the signer subset" + ); + let proposer_requests = self.requests.entry(proposer).or_default(); + proposer_requests.insert(request.receipt_id, request); + } else { + tracing::info!( + receipt_id = %request.receipt_id, + ?subset, + ?proposer, + "skipping sign request: node is NOT in the signer subset" + ); + } + } + } + + pub fn contains(&self, participant: Participant, receipt_id: CryptoHash) -> bool { + let Some(participant_requests) = self.requests.get(&participant) else { + return false; + }; + participant_requests.contains_key(&receipt_id) + } + + pub fn my_requests(&mut self, me: Participant) -> &mut HashMap { + self.requests.entry(me).or_default() + } +} + +/// An ongoing signature generator. +pub struct SignatureGenerator { + pub protocol: SignatureProtocol, + pub proposer: Participant, + pub presignature_id: PresignatureId, + pub msg_hash: [u8; 32], +} + +pub struct SignatureManager { + /// Ongoing signature generation protocols. + generators: HashMap, + /// Generated signatures assigned to the current node that are yet to be published. + signatures: Vec<(CryptoHash, FullSignature)>, + + participants: Vec, + me: Participant, + public_key: PublicKey, + epoch: u64, +} + +impl SignatureManager { + pub fn new( + participants: Vec, + me: Participant, + public_key: PublicKey, + epoch: u64, + ) -> Self { + Self { + generators: HashMap::new(), + signatures: Vec::new(), + participants, + me, + public_key, + epoch, + } + } + + fn generate_internal( + participants: &[Participant], + me: Participant, + public_key: PublicKey, + proposer: Participant, + presignature: Presignature, + msg_hash: [u8; 32], + ) -> Result { + let protocol = Box::new(cait_sith::sign( + participants, + me, + public_key, + presignature.output, + Scalar::from_uint_unchecked(U256::from_le_slice(&msg_hash)), + )?); + Ok(SignatureGenerator { + protocol, + proposer, + presignature_id: presignature.id, + msg_hash, + }) + } + + /// Starts a new presignature generation protocol. + pub fn generate( + &mut self, + receipt_id: CryptoHash, + presignature: Presignature, + public_key: PublicKey, + msg_hash: [u8; 32], + ) -> Result<(), InitializationError> { + tracing::info!(%receipt_id, "starting protocol to generate a new signature"); + let generator = Self::generate_internal( + &self.participants, + self.me, + public_key, + self.me, + presignature, + msg_hash, + )?; + self.generators.insert(receipt_id, generator); + Ok(()) + } + + /// Ensures that the presignature with the given id is either: + /// 1) Already generated in which case returns `None`, or + /// 2) Is currently being generated by `protocol` in which case returns `Some(protocol)`, or + /// 3) Has never been seen by the manager in which case start a new protocol and returns `Some(protocol)`, or + /// 4) Depends on triples (`triple0`/`triple1`) that are unknown to the node + // TODO: What if the presignature completed generation and is already spent? + pub fn get_or_generate( + &mut self, + receipt_id: CryptoHash, + proposer: Participant, + presignature_id: PresignatureId, + msg_hash: [u8; 32], + presignature_manager: &mut PresignatureManager, + ) -> Result, InitializationError> { + match self.generators.entry(receipt_id) { + Entry::Vacant(entry) => { + tracing::info!(%receipt_id, "joining protocol to generate a new signature"); + let Some(presignature) = presignature_manager.take(presignature_id) else { + tracing::warn!(presignature_id, "presignature is missing, can't join"); + return Ok(None); + }; + let generator = Self::generate_internal( + &self.participants, + self.me, + self.public_key, + proposer, + presignature, + msg_hash, + )?; + let generator = entry.insert(generator); + Ok(Some(&mut generator.protocol)) + } + Entry::Occupied(entry) => Ok(Some(&mut entry.into_mut().protocol)), + } + } + + /// Pokes all of the ongoing generation protocols and returns a vector of + /// messages to be sent to the respective participant. + /// + /// An empty vector means we cannot progress until we receive a new message. + pub fn poke(&mut self) -> Result, ProtocolError> { + let mut messages = Vec::new(); + let mut result = Ok(()); + self.generators.retain(|receipt_id, generator| { + loop { + let action = match generator.protocol.poke() { + Ok(action) => action, + Err(e) => { + result = Err(e); + break false; + } + }; + match action { + Action::Wait => { + tracing::debug!("waiting"); + // Retain protocol until we are finished + return true; + } + Action::SendMany(data) => { + for p in &self.participants { + messages.push(( + *p, + SignatureMessage { + receipt_id: *receipt_id, + proposer: generator.proposer, + presignature_id: generator.presignature_id, + msg_hash: generator.msg_hash, + epoch: self.epoch, + from: self.me, + data: data.clone(), + }, + )) + } + } + Action::SendPrivate(p, data) => messages.push(( + p, + SignatureMessage { + receipt_id: *receipt_id, + proposer: generator.proposer, + presignature_id: generator.presignature_id, + msg_hash: generator.msg_hash, + epoch: self.epoch, + from: self.me, + data: data.clone(), + }, + )), + Action::Return(output) => { + tracing::info!( + ?receipt_id, + big_r = ?output.big_r.to_base58(), + s = ?output.s, + "completed signature generation" + ); + if generator.proposer == self.me { + self.signatures.push((*receipt_id, output)); + } + // Do not retain the protocol + return false; + } + } + } + }); + result.map(|_| messages) + } + + pub async fn publish( + &mut self, + rpc_client: &near_fetch::Client, + signer: &T, + mpc_contract_id: &AccountId, + ) -> Result<(), near_fetch::Error> { + for (receipt_id, signature) in self.signatures.drain(..) { + // TODO: Figure out how to properly serialize the signature + // let r_s = signature.big_r.x().concat(signature.s.to_bytes()); + // let tag = + // ConditionallySelectable::conditional_select(&2u8, &3u8, signature.big_r.y_is_odd()); + // let signature = r_s.append(tag); + // let signature = Secp256K1Signature::try_from(signature.as_slice()).unwrap(); + // let signature = Signature::SECP256K1(signature); + tracing::info!(%receipt_id, big_r = signature.big_r.to_base58(), s = ?signature.s, "publishing signature response"); + rpc_client + .send_tx( + signer, + mpc_contract_id, + vec![near_primitives::transaction::Action::FunctionCall( + FunctionCallAction { + method_name: "respond".to_string(), + args: serde_json::to_vec(&serde_json::json!({ + "receipt_id": receipt_id.as_bytes(), + "big_r": signature.big_r, + "s": signature.s + })) + .unwrap(), + gas: 300_000_000_000_000, + deposit: 0, + }, + )], + ) + .await?; + } + Ok(()) + } +} diff --git a/node/src/protocol/state.rs b/node/src/protocol/state.rs index 8c1d19b88..a6275d1d0 100644 --- a/node/src/protocol/state.rs +++ b/node/src/protocol/state.rs @@ -1,8 +1,12 @@ use super::presignature::PresignatureManager; +use super::signature::SignatureManager; use super::triple::TripleManager; +use super::SignQueue; use crate::types::{KeygenProtocol, PrivateKeyShare, PublicKey, ReshareProtocol}; use cait_sith::protocol::Participant; -use std::collections::HashMap; +use std::collections::BTreeMap; +use std::sync::Arc; +use tokio::sync::RwLock; use url::Url; pub struct PersistentNodeData { @@ -14,14 +18,14 @@ pub struct PersistentNodeData { pub struct StartedState(pub Option); pub struct GeneratingState { - pub participants: HashMap, + pub participants: BTreeMap, pub threshold: usize, pub protocol: KeygenProtocol, } pub struct WaitingForConsensusState { pub epoch: u64, - pub participants: HashMap, + pub participants: BTreeMap, pub threshold: usize, pub private_share: PrivateKeyShare, pub public_key: PublicKey, @@ -29,18 +33,20 @@ pub struct WaitingForConsensusState { pub struct RunningState { pub epoch: u64, - pub participants: HashMap, + pub participants: BTreeMap, pub threshold: usize, pub private_share: PrivateKeyShare, pub public_key: PublicKey, + pub sign_queue: Arc>, pub triple_manager: TripleManager, pub presignature_manager: PresignatureManager, + pub signature_manager: SignatureManager, } pub struct ResharingState { pub old_epoch: u64, - pub old_participants: HashMap, - pub new_participants: HashMap, + pub old_participants: BTreeMap, + pub new_participants: BTreeMap, pub threshold: usize, pub public_key: PublicKey, pub protocol: ReshareProtocol, diff --git a/node/src/protocol/triple.rs b/node/src/protocol/triple.rs index ca5983d96..d3b2cce9b 100644 --- a/node/src/protocol/triple.rs +++ b/node/src/protocol/triple.rs @@ -80,7 +80,7 @@ impl TripleManager { /// Starts a new Beaver triple generation protocol. pub fn generate(&mut self) -> Result<(), InitializationError> { let id = rand::random(); - tracing::info!(id, "starting protocol to generate a new triple"); + tracing::debug!(id, "starting protocol to generate a new triple"); let protocol: TripleProtocol = Box::new(cait_sith::triples::generate_triple( &self.participants, self.me, @@ -96,18 +96,27 @@ impl TripleManager { Ok(()) } - /// Take an unspent triple by its id with no way to return it. + /// Take two unspent triple by theirs id with no way to return it. Only takes + /// if both of them are present. /// It is very important to NOT reuse the same triple twice for two different /// protocols. - pub fn take(&mut self, id: TripleId) -> Option { - self.triples.remove(&id) + pub fn take_two(&mut self, id0: TripleId, id1: TripleId) -> Result<(Triple, Triple), TripleId> { + if !self.triples.contains_key(&id0) { + Err(id0) + } else if !self.triples.contains_key(&id1) { + Err(id1) + } else { + Ok(( + self.triples.remove(&id0).unwrap(), + self.triples.remove(&id1).unwrap(), + )) + } } /// Take two random unspent triple generated by this node. Either takes both or none. /// It is very important to NOT reuse the same triple twice for two different /// protocols. pub fn take_mine_twice(&mut self) -> Option<(Triple, Triple)> { - tracing::info!(mine = ?self.mine, "my triples"); if self.mine.len() < 2 { return None; } @@ -139,7 +148,7 @@ impl TripleManager { } else { match self.generators.entry(id) { Entry::Vacant(e) => { - tracing::info!(id, "joining protocol to generate a new triple"); + tracing::debug!(id, "joining protocol to generate a new triple"); let protocol = Box::new(cait_sith::triples::generate_triple( &self.participants, self.me, diff --git a/node/src/rpc_client.rs b/node/src/rpc_client.rs index c6b386164..72341f583 100644 --- a/node/src/rpc_client.rs +++ b/node/src/rpc_client.rs @@ -22,6 +22,7 @@ pub async fn vote_for_public_key( mpc_contract_id: &AccountId, public_key: &near_crypto::PublicKey, ) -> anyhow::Result { + tracing::info!(%public_key, "voting for public key"); let args = json!({ "public_key": public_key }); diff --git a/node/src/types.rs b/node/src/types.rs index 34b03c830..a69b56ba5 100644 --- a/node/src/types.rs +++ b/node/src/types.rs @@ -1,6 +1,6 @@ use cait_sith::triples::TripleGenerationOutput; -use cait_sith::PresignOutput; use cait_sith::{protocol::Protocol, KeygenOutput}; +use cait_sith::{FullSignature, PresignOutput}; use k256::{elliptic_curve::CurveArithmetic, Secp256k1}; pub type PrivateKeyShare = ::Scalar; @@ -10,3 +10,4 @@ pub type ReshareProtocol = Box + Send + S pub type TripleProtocol = Box> + Send + Sync>; pub type PresignatureProtocol = Box> + Send + Sync>; +pub type SignatureProtocol = Box> + Send + Sync>;