diff --git a/CHANGELOG.md b/CHANGELOG.md index d0e369a9e..ebbd6d694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Improved tracing span fields ([#1650](https://github.com/0xMiden/miden-node/pull/1650)) - Replaced NTX Builder's in-memory state management with SQLite-backed persistence; account states, notes, and transaction effects are now stored in the database and inflight state is purged on startup ([#1662](https://github.com/0xMiden/miden-node/pull/1662)). - [BREAKING] Reworked `miden-remote-prover`, removing the `worker`/`proxy` distinction and simplifying to a `worker` with a request queue ([#1688](https://github.com/0xMiden/miden-node/pull/1688)). +- Added KMS signing support in validator ([#1677](https://github.com/0xMiden/miden-node/pull/1677)). ## v0.13.5 (2026-02-19) diff --git a/Cargo.lock b/Cargo.lock index e3f772dbd..fdb3d3e66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,6 +175,395 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8fc176d53d6fe85017f230405e3255cedb4a02221cb55ed6d76dccbbb099b2" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.4.0", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e26bbf46abc608f2dc61fd6cb3b7b0665497cc259a21520151ed98f8b37d2c79" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f92058d22a46adf53ec57a6a96f34447daf02bff52e8fb956c66bcd5c6ac12" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 1.4.0", + "http-body 1.0.1", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.100.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723700afe7459a33d1ac30852e9208b801946c032625cc8c808f57b9563bb5c7" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.94.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "699da1961a289b23842d88fe2984c6ff68735fdf9bdcbc69ceaeb2491c9bf434" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e3a4cb3b124833eafea9afd1a6cc5f8ddf3efefffc6651ef76a03cbc6b4981" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.98.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c4f19655ab0856375e169865c91264de965bd74c407c7f1e403184b1049409" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f6ae9b71597dc5fd115d52849d7a5556ad9265885ad3492ea8d73b93bbc46e" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cba48474f1d6807384d06fec085b909f5807e16653c5af5c45dfe89539f0b70" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.63.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4a8a5fe3e4ac7ee871237c340bbce13e982d37543b65700f4419e039f5d78e" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0709f0083aa19b704132684bc26d3c868e06bd428ccc4373b0b55c3e8748a58b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.8.1", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.62.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b3a779093e18cad88bbae08dc4261e1d95018c4c5b9356a52bcae7c0b6e9bb" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3f39d5bb871aaf461d59144557f16d5927a5248a983a40654d9cf3b9ba183b" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f76a580e3d8f8961e5d48763214025a2af65c2fa4cd1fb7f270a0e107a71b0" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd3dfc18c1ce097cf81fced7192731e63809829c6cbf933c1ec47452d08e1aa" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c55e0837e9b8526f49e0b9bfa9ee18ddee70e853f5bc09c5d11ebceddcb0fec" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576b0d6991c9c32bc14fc340582ef148311f924d41815f641a308b5d11e8e7cd" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53543b4b86ed43f051644f704a98c7291b3618b67adf057ee77a366fa52fcaa" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c50f3cdf47caa8d01f2be4a6663ea02418e892f9bbfd82c7b9a3a37eaccdd3a" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] + [[package]] name = "axum" version = "0.8.8" @@ -185,10 +574,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "itoa", "matchit", @@ -216,8 +605,8 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", @@ -263,6 +652,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.8.3" @@ -380,6 +779,16 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bzip2-sys" version = "0.1.13+1.0.8" @@ -549,6 +958,15 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -940,6 +1358,12 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1168,6 +1592,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.31" @@ -1339,6 +1769,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.13" @@ -1350,7 +1799,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap", "slab", "tokio", @@ -1419,6 +1868,17 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -1429,6 +1889,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1436,7 +1907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1447,8 +1918,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1470,6 +1941,30 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.8.1" @@ -1480,9 +1975,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -1493,19 +1988,35 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", - "hyper", + "http 1.4.0", + "hyper 1.8.1", "hyper-util", - "rustls", + "rustls 0.23.36", + "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower-service", ] @@ -1515,7 +2026,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper", + "hyper 1.8.1", "hyper-util", "pin-project-lite", "tokio", @@ -1530,7 +2041,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -1549,14 +2060,14 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -2192,9 +2703,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.19.4" +version = "0.19.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e28b6e110f339c2edc2760a8cb94863f0a055ee658a49bc90c8560eff2feef4" +checksum = "999926d48cf0929a39e06ce22299084f11d307ca9e765801eb56bf192b07054b" dependencies = [ "blake3", "cc", @@ -2462,7 +2973,7 @@ dependencies = [ "assert_matches", "fs-err", "hex", - "http", + "http 1.4.0", "miden-node-grpc-error-macro", "miden-node-proto-build", "miden-node-rocksdb-cxx-linkage-fix", @@ -2499,7 +3010,7 @@ version = "0.14.0" dependencies = [ "anyhow", "futures", - "http", + "http 1.4.0", "mediatype", "miden-air", "miden-node-proto", @@ -2606,9 +3117,10 @@ name = "miden-node-utils" version = "0.14.0" dependencies = [ "anyhow", + "async-trait", "bytes", "figment", - "http", + "http 1.4.0", "http-body-util", "itertools 0.14.0", "lru", @@ -2635,8 +3147,12 @@ name = "miden-node-validator" version = "0.14.0" dependencies = [ "anyhow", + "async-trait", + "aws-config", + "aws-sdk-kms", "diesel", "diesel_migrations", + "k256", "miden-node-db", "miden-node-proto", "miden-node-proto-build", @@ -2733,7 +3249,7 @@ dependencies = [ "anyhow", "async-trait", "clap", - "http", + "http 1.4.0", "humantime", "miden-block-prover", "miden-node-proto", @@ -3253,7 +3769,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2366db2dca4d2ad033cad11e6ee42844fd727007af5ad04a1730f4cb8163bf" dependencies = [ - "http", + "http 1.4.0", "opentelemetry", "opentelemetry-proto", "opentelemetry_sdk", @@ -3293,6 +3809,12 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "owo-colors" version = "4.2.3" @@ -3840,6 +4362,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.8" @@ -3862,12 +4390,12 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.8.1", + "hyper-rustls 0.27.7", "hyper-tls", "hyper-util", "js-sys", @@ -4021,17 +4549,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] @@ -4057,12 +4598,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -4131,6 +4683,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sdd" version = "3.0.10" @@ -4402,6 +4964,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.2" @@ -4732,7 +5304,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -4758,13 +5330,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.36", "tokio", ] @@ -4895,20 +5477,20 @@ dependencies = [ "axum", "base64", "bytes", - "h2", - "http", - "http-body", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", "rustls-native-certs", - "socket2", + "socket2 0.6.2", "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-stream", "tower", "tower-layer", @@ -4990,8 +5572,8 @@ checksum = "75214f6b6bd28c19aa752ac09fdf0eea546095670906c21fe3940e180a4c43f2" dependencies = [ "base64", "bytes", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project", "tokio-stream", "tonic", @@ -5010,8 +5592,8 @@ dependencies = [ "byteorder", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "httparse", "js-sys", @@ -5053,8 +5635,8 @@ dependencies = [ "bitflags", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "iri-string", "pin-project-lite", @@ -5288,6 +5870,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -5300,6 +5888,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -5318,6 +5916,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "vte" version = "0.14.1" @@ -5916,6 +6520,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yansi" version = "1.0.1" diff --git a/bin/node/.env b/bin/node/.env index 6bdfa9a80..02bceb57e 100644 --- a/bin/node/.env +++ b/bin/node/.env @@ -11,6 +11,7 @@ MIDEN_NODE_STORE_NTX_BUILDER_URL= MIDEN_NODE_STORE_BLOCK_PRODUCER_URL= MIDEN_NODE_VALIDATOR_BLOCK_PRODUCER_URL= MIDEN_NODE_VALIDATOR_KEY= +MIDEN_NODE_VALIDATOR_KMS_KEY_ID= MIDEN_NODE_RPC_URL=http://0.0.0.0:57291 MIDEN_NODE_DATA_DIRECTORY=./ MIDEN_NODE_ENABLE_OTEL=true diff --git a/bin/node/Dockerfile b/bin/node/Dockerfile index 9778daec8..30aef2637 100644 --- a/bin/node/Dockerfile +++ b/bin/node/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.90-slim-bullseye AS chef +FROM rust:1.91-slim-bookworm AS chef # Install build dependencies. RocksDB is compiled from source by librocksdb-sys. RUN apt-get update && \ apt-get -y upgrade && \ diff --git a/bin/node/src/commands/bundled.rs b/bin/node/src/commands/bundled.rs index 707e01193..9ca187207 100644 --- a/bin/node/src/commands/bundled.rs +++ b/bin/node/src/commands/bundled.rs @@ -7,7 +7,7 @@ use miden_node_block_producer::BlockProducer; use miden_node_rpc::Rpc; use miden_node_store::Store; use miden_node_utils::grpc::UrlExt; -use miden_node_validator::Validator; +use miden_node_validator::{Validator, ValidatorSigner}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::Deserializable; use tokio::net::TcpListener; @@ -17,14 +17,13 @@ use url::Url; use super::{ENV_DATA_DIRECTORY, ENV_RPC_URL}; use crate::commands::{ BlockProducerConfig, + BundledValidatorConfig, DEFAULT_TIMEOUT, ENV_BLOCK_PROVER_URL, ENV_ENABLE_OTEL, ENV_GENESIS_CONFIG_FILE, - ENV_VALIDATOR_KEY, - INSECURE_VALIDATOR_KEY_HEX, NtxBuilderConfig, - ValidatorConfig, + ValidatorKey, duration_to_human_readable_string, }; @@ -47,16 +46,9 @@ pub enum BundledCommand { /// Constructs the genesis block from the given toml file. #[arg(long, env = ENV_GENESIS_CONFIG_FILE, value_name = "FILE")] genesis_config_file: Option, - /// Insecure, hex-encoded validator secret key for development and testing purposes. - /// - /// If not provided, a predefined key is used. - #[arg( - long = "validator.key", - env = ENV_VALIDATOR_KEY, - value_name = "VALIDATOR_KEY", - default_value = INSECURE_VALIDATOR_KEY_HEX - )] - validator_key: String, + /// Configuration for the Validator key used to sign genesis block. + #[command(flatten)] + validator_key: ValidatorKey, }, /// Runs all three node components in the same process. @@ -83,7 +75,7 @@ pub enum BundledCommand { ntx_builder: NtxBuilderConfig, #[command(flatten)] - validator: ValidatorConfig, + validator: BundledValidatorConfig, /// Enables the exporting of traces for OpenTelemetry. /// @@ -156,7 +148,7 @@ impl BundledCommand { data_directory: PathBuf, block_producer: BlockProducerConfig, ntx_builder: NtxBuilderConfig, - validator: ValidatorConfig, + validator: BundledValidatorConfig, grpc_timeout: Duration, ) -> anyhow::Result<()> { // Start listening on all gRPC urls so that inter-component connections can be created @@ -313,6 +305,7 @@ impl BundledCommand { if let Some(address) = validator_socket_address { let secret_key_bytes = hex::decode(validator.validator_key)?; let signer = SecretKey::read_from_bytes(&secret_key_bytes)?; + let signer = ValidatorSigner::new_local(signer); let id = join_set .spawn({ async move { diff --git a/bin/node/src/commands/mod.rs b/bin/node/src/commands/mod.rs index 352a6de16..b7ef3c3c5 100644 --- a/bin/node/src/commands/mod.rs +++ b/bin/node/src/commands/mod.rs @@ -10,6 +10,9 @@ use miden_node_block_producer::{ DEFAULT_MAX_BATCHES_PER_BLOCK, DEFAULT_MAX_TXS_PER_BATCH, }; +use miden_node_validator::ValidatorSigner; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::utils::Deserializable; use tokio::net::TcpListener; use url::Url; @@ -41,6 +44,7 @@ const ENV_MAX_BATCHES_PER_BLOCK: &str = "MIDEN_MAX_BATCHES_PER_BLOCK"; const ENV_MEMPOOL_TX_CAPACITY: &str = "MIDEN_NODE_MEMPOOL_TX_CAPACITY"; const ENV_NTX_SCRIPT_CACHE_SIZE: &str = "MIDEN_NTX_DATA_STORE_SCRIPT_CACHE_SIZE"; const ENV_VALIDATOR_KEY: &str = "MIDEN_NODE_VALIDATOR_KEY"; +const ENV_VALIDATOR_KMS_KEY_ID: &str = "MIDEN_NODE_VALIDATOR_KMS_KEY_ID"; const DEFAULT_NTX_TICKER_INTERVAL: Duration = Duration::from_millis(200); const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10); @@ -51,9 +55,55 @@ fn duration_to_human_readable_string(duration: Duration) -> String { humantime::format_duration(duration).to_string() } -/// Configuration for the Validator component. +/// Configuration for the Validator key used to sign blocks. +/// +/// Used by the Validator command and the genesis bootstrap command. #[derive(clap::Args)] -pub struct ValidatorConfig { +#[group(required = true, multiple = false)] +pub struct ValidatorKey { + /// Insecure, hex-encoded validator secret key for development and testing purposes. + /// + /// If not provided, a predefined key is used. + /// + /// Cannot be used with `validator.key.kms-id`. + #[arg( + long = "validator.key.hex", + env = ENV_VALIDATOR_KEY, + value_name = "VALIDATOR_KEY", + default_value = INSECURE_VALIDATOR_KEY_HEX, + )] + validator_key: String, + /// Key ID for the KMS key used by validator to sign blocks. + /// + /// Cannot be used with `validator.key.hex`. + #[arg( + long = "validator.key.kms-id", + env = ENV_VALIDATOR_KMS_KEY_ID, + value_name = "VALIDATOR_KMS_KEY_ID", + )] + validator_kms_key_id: Option, +} + +impl ValidatorKey { + /// Consumes the validator key configuration and returns a KMS or local key signer depending on + /// the supplied configuration. + pub async fn into_signer(self) -> anyhow::Result { + if let Some(kms_key_id) = self.validator_kms_key_id { + // Use KMS key ID to create a ValidatorSigner. + let signer = ValidatorSigner::new_kms(kms_key_id).await?; + Ok(signer) + } else { + // Use hex-encoded key to create a ValidatorSigner. + let signer = SecretKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?; + let signer = ValidatorSigner::new_local(signer); + Ok(signer) + } + } +} + +/// Configuration for the Validator component when run in the bundled mode. +#[derive(clap::Args)] +pub struct BundledValidatorConfig { /// Insecure, hex-encoded validator secret key for development and testing purposes. /// Only used when the Validator URL argument is not set. #[arg( @@ -70,7 +120,7 @@ pub struct ValidatorConfig { validator_url: Option, } -impl ValidatorConfig { +impl BundledValidatorConfig { /// Converts the [`ValidatorConfig`] into a URL and an optional [`SocketAddr`]. /// /// If the `validator_url` is set, it returns the URL and `None` for the [`SocketAddr`]. diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 7bf56f4a8..14b266147 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -5,8 +5,8 @@ use anyhow::Context; use miden_node_store::Store; use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; use miden_node_utils::grpc::UrlExt; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; -use miden_protocol::utils::Deserializable; +use miden_node_utils::signer::BlockSigner; +use miden_node_validator::ValidatorSigner; use url::Url; use super::{ @@ -20,8 +20,7 @@ use crate::commands::{ ENV_BLOCK_PROVER_URL, ENV_ENABLE_OTEL, ENV_GENESIS_CONFIG_FILE, - ENV_VALIDATOR_KEY, - INSECURE_VALIDATOR_KEY_HEX, + ValidatorKey, duration_to_human_readable_string, }; @@ -44,18 +43,9 @@ pub enum StoreCommand { /// Use the given configuration file to construct the genesis state from. #[arg(long, env = ENV_GENESIS_CONFIG_FILE, value_name = "GENESIS_CONFIG")] genesis_config_file: Option, - /// Insecure, hex-encoded validator secret key for development and testing purposes. - /// - /// Used to sign the genesis block in the bootstrap process. - /// - /// If not provided, a predefined key is used. - #[arg( - long = "validator.key", - env = ENV_VALIDATOR_KEY, - value_name = "VALIDATOR_KEY", - default_value = INSECURE_VALIDATOR_KEY_HEX - )] - validator_key: String, + /// Configuration for the Validator key used to sign genesis block. + #[command(flatten)] + validator_key: ValidatorKey, }, /// Starts the store component. @@ -112,12 +102,15 @@ impl StoreCommand { accounts_directory, genesis_config_file, validator_key, - } => Self::bootstrap( - &data_directory, - &accounts_directory, - genesis_config_file.as_ref(), - validator_key, - ), + } => { + Self::bootstrap( + &data_directory, + &accounts_directory, + genesis_config_file.as_ref(), + validator_key, + ) + .await + }, StoreCommand::Start { rpc_url, ntx_builder_url, @@ -190,15 +183,12 @@ impl StoreCommand { .context("failed while serving store component") } - fn bootstrap( + async fn bootstrap( data_directory: &Path, accounts_directory: &Path, genesis_config: Option<&PathBuf>, - validator_key: String, + validator_key: ValidatorKey, ) -> anyhow::Result<()> { - // Decode the validator key. - let signer = SecretKey::read_from_bytes(&hex::decode(validator_key)?)?; - // Parse genesis config (or default if not given). let config = genesis_config .map(|file_path| { @@ -209,8 +199,6 @@ impl StoreCommand { .transpose()? .unwrap_or_default(); - let (genesis_state, secrets) = config.into_state(signer)?; - // Create directories if they do not already exist. for directory in &[accounts_directory, data_directory] { if fs_err::exists(directory)? { @@ -233,7 +221,41 @@ impl StoreCommand { } } - // Write the accounts to disk + // Bootstrap with KMS key or local key. + let signer = validator_key.into_signer().await?; + match signer { + ValidatorSigner::Kms(signer) => { + Self::bootstrap_accounts_and_store( + config, + signer, + accounts_directory, + data_directory, + ) + .await + }, + ValidatorSigner::Local(signer) => { + Self::bootstrap_accounts_and_store( + config, + signer, + accounts_directory, + data_directory, + ) + .await + }, + } + } + + /// Builds the genesis state of the chain, writes accounts to file, and bootstraps the store. + async fn bootstrap_accounts_and_store( + config: GenesisConfig, + signer: impl BlockSigner, + accounts_directory: &Path, + data_directory: &Path, + ) -> anyhow::Result<()> { + // Build genesis state with the provided signer. + let (genesis_state, secrets) = config.into_state(signer)?; + + // Write accounts to file. for item in secrets.as_account_files(&genesis_state) { let AccountFileWithName { account_file, name } = item?; let accountpath = accounts_directory.join(name); @@ -246,6 +268,7 @@ impl StoreCommand { account_file.write(accountpath)?; } - Store::bootstrap(genesis_state, data_directory) + // Bootstrap store. + Store::bootstrap(genesis_state, data_directory).await } } diff --git a/bin/node/src/commands/validator.rs b/bin/node/src/commands/validator.rs index 461e446c1..ef3d9363a 100644 --- a/bin/node/src/commands/validator.rs +++ b/bin/node/src/commands/validator.rs @@ -1,9 +1,10 @@ +use std::net::SocketAddr; use std::path::PathBuf; use std::time::Duration; use anyhow::Context; use miden_node_utils::grpc::UrlExt; -use miden_node_validator::Validator; +use miden_node_validator::{Validator, ValidatorSigner}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::Deserializable; use url::Url; @@ -13,6 +14,7 @@ use crate::commands::{ ENV_DATA_DIRECTORY, ENV_ENABLE_OTEL, ENV_VALIDATOR_KEY, + ENV_VALIDATOR_KMS_KEY_ID, ENV_VALIDATOR_URL, INSECURE_VALIDATOR_KEY_HEX, duration_to_human_readable_string, @@ -49,26 +51,63 @@ pub enum ValidatorCommand { /// Insecure, hex-encoded validator secret key for development and testing purposes. /// /// If not provided, a predefined key is used. - #[arg(long = "key", env = ENV_VALIDATOR_KEY, value_name = "VALIDATOR_KEY", default_value = INSECURE_VALIDATOR_KEY_HEX)] + /// + /// Cannot be used with `key.kms-id`. + #[arg( + long = "key.hex", + env = ENV_VALIDATOR_KEY, + value_name = "VALIDATOR_KEY", + default_value = INSECURE_VALIDATOR_KEY_HEX, + group = "key" + )] validator_key: String, + + /// Key ID for the KMS key used by validator to sign blocks. + /// + /// Cannot be used with `key.hex`. + #[arg( + long = "key.kms-id", + env = ENV_VALIDATOR_KMS_KEY_ID, + value_name = "VALIDATOR_KMS_KEY_ID", + group = "key" + )] + kms_key_id: Option, }, } impl ValidatorCommand { + /// Runs the validator command. pub async fn handle(self) -> anyhow::Result<()> { let Self::Start { url, grpc_timeout, validator_key, data_directory, + kms_key_id, .. } = self; let address = url.to_socket().context("Failed to extract socket address from validator URL")?; - let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; + // Run validator with KMS key backend if key id provided. + if let Some(kms_key_id) = kms_key_id { + let signer = ValidatorSigner::new_kms(kms_key_id).await?; + Self::serve(address, grpc_timeout, signer, data_directory).await + } else { + let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; + let signer = ValidatorSigner::new_local(signer); + Self::serve(address, grpc_timeout, signer, data_directory).await + } + } + /// Runs the validator component until failure. + async fn serve( + address: SocketAddr, + grpc_timeout: Duration, + signer: ValidatorSigner, + data_directory: PathBuf, + ) -> anyhow::Result<()> { Validator { address, grpc_timeout, diff --git a/bin/stress-test/src/seeding/mod.rs b/bin/stress-test/src/seeding/mod.rs index 3b80481bb..c77d50c77 100644 --- a/bin/stress-test/src/seeding/mod.rs +++ b/bin/stress-test/src/seeding/mod.rs @@ -93,7 +93,9 @@ pub async fn seed_store( let fee_params = FeeParameters::new(faucet.id(), 0).unwrap(); let signer = EcdsaSecretKey::new(); let genesis_state = GenesisState::new(vec![faucet.clone()], fee_params, 1, 1, signer); - Store::bootstrap(genesis_state.clone(), &data_directory).expect("store should bootstrap"); + Store::bootstrap(genesis_state.clone(), &data_directory) + .await + .expect("store should bootstrap"); // start the store let (_, store_url) = start_store(data_directory.clone()).await; @@ -103,7 +105,7 @@ pub async fn seed_store( let accounts_filepath = data_directory.join(ACCOUNTS_FILENAME); let data_directory = miden_node_store::DataDirectory::load(data_directory).expect("data directory should exist"); - let genesis_header = genesis_state.into_block().unwrap().into_inner(); + let genesis_header = genesis_state.into_block().await.unwrap().into_inner(); let metrics = generate_blocks( num_accounts, public_accounts_percentage, diff --git a/crates/block-producer/src/server/tests.rs b/crates/block-producer/src/server/tests.rs index 8c98e9da4..b97a946a9 100644 --- a/crates/block-producer/src/server/tests.rs +++ b/crates/block-producer/src/server/tests.rs @@ -4,7 +4,7 @@ use std::time::Duration; use miden_node_proto::generated::block_producer::api_client as block_producer_client; use miden_node_store::{GenesisState, Store}; use miden_node_utils::fee::test_fee_params; -use miden_node_validator::Validator; +use miden_node_validator::{Validator, ValidatorSigner}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::testing::random_signer::RandomBlockSigner as _; use tokio::net::TcpListener; @@ -49,7 +49,7 @@ async fn block_producer_startup_is_robust_to_network_failures() { Validator { address: validator_addr, grpc_timeout, - signer: SecretKey::random(), + signer: ValidatorSigner::new_local(SecretKey::random()), data_directory, } .serve() @@ -131,7 +131,9 @@ async fn start_store( data_directory: &std::path::Path, ) -> runtime::Runtime { let genesis_state = GenesisState::new(vec![], test_fee_params(), 1, 1, SecretKey::random()); - Store::bootstrap(genesis_state.clone(), data_directory).expect("store should bootstrap"); + Store::bootstrap(genesis_state.clone(), data_directory) + .await + .expect("store should bootstrap"); let dir = data_directory.to_path_buf(); let rpc_listener = diff --git a/crates/rpc/src/tests.rs b/crates/rpc/src/tests.rs index e70d14563..9c69d2818 100644 --- a/crates/rpc/src/tests.rs +++ b/crates/rpc/src/tests.rs @@ -407,7 +407,9 @@ async fn start_store(store_addr: SocketAddr) -> (Runtime, TempDir, Word) { let config = GenesisConfig::default(); let signer = SecretKey::new(); let (genesis_state, _) = config.into_state(signer).unwrap(); - Store::bootstrap(genesis_state.clone(), data_directory.path()).expect("store should bootstrap"); + Store::bootstrap(genesis_state.clone(), data_directory.path()) + .await + .expect("store should bootstrap"); let dir = data_directory.path().to_path_buf(); let rpc_listener = TcpListener::bind(store_addr).await.expect("store should bind a port"); let ntx_builder_listener = TcpListener::bind("127.0.0.1:0") @@ -436,7 +438,7 @@ async fn start_store(store_addr: SocketAddr) -> (Runtime, TempDir, Word) { ( store_runtime, data_directory, - genesis_state.into_block().unwrap().inner().header().commitment(), + genesis_state.into_block().await.unwrap().inner().header().commitment(), ) } diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 2154af4c3..706e20210 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -1383,9 +1383,9 @@ fn test_select_account_code_by_commitment_multiple_codes() { // ================================================================================================ /// Verifies genesis block with account containing vault assets can be inserted. -#[test] +#[tokio::test] #[miden_node_test_macro::enable_logging] -fn genesis_with_account_assets() { +async fn genesis_with_account_assets() { use crate::genesis::GenesisState; let component_code = "pub proc foo push.1 end"; @@ -1410,15 +1410,15 @@ fn genesis_with_account_assets() { let genesis_state = GenesisState::new(vec![account], test_fee_params(), 1, 0, SecretKey::random()); - let genesis_block = genesis_state.into_block().unwrap(); + let genesis_block = genesis_state.into_block().await.unwrap(); crate::db::Db::bootstrap(":memory:".into(), &genesis_block).unwrap(); } /// Verifies genesis block with account containing storage maps can be inserted. -#[test] +#[tokio::test] #[miden_node_test_macro::enable_logging] -fn genesis_with_account_storage_map() { +async fn genesis_with_account_storage_map() { use miden_protocol::account::StorageMap; use crate::genesis::GenesisState; @@ -1459,15 +1459,15 @@ fn genesis_with_account_storage_map() { let genesis_state = GenesisState::new(vec![account], test_fee_params(), 1, 0, SecretKey::random()); - let genesis_block = genesis_state.into_block().unwrap(); + let genesis_block = genesis_state.into_block().await.unwrap(); crate::db::Db::bootstrap(":memory:".into(), &genesis_block).unwrap(); } /// Verifies genesis block with account containing both vault assets and storage maps. -#[test] +#[tokio::test] #[miden_node_test_macro::enable_logging] -fn genesis_with_account_assets_and_storage() { +async fn genesis_with_account_assets_and_storage() { use miden_protocol::account::StorageMap; use crate::genesis::GenesisState; @@ -1506,16 +1506,16 @@ fn genesis_with_account_assets_and_storage() { let genesis_state = GenesisState::new(vec![account], test_fee_params(), 1, 0, SecretKey::random()); - let genesis_block = genesis_state.into_block().unwrap(); + let genesis_block = genesis_state.into_block().await.unwrap(); crate::db::Db::bootstrap(":memory:".into(), &genesis_block).unwrap(); } /// Verifies genesis block with multiple accounts of different types. /// Tests realistic genesis scenario with basic accounts, assets, and storage. -#[test] +#[tokio::test] #[miden_node_test_macro::enable_logging] -fn genesis_with_multiple_accounts() { +async fn genesis_with_multiple_accounts() { use miden_protocol::account::StorageMap; use crate::genesis::GenesisState; @@ -1584,7 +1584,7 @@ fn genesis_with_multiple_accounts() { 0, SecretKey::random(), ); - let genesis_block = genesis_state.into_block().unwrap(); + let genesis_block = genesis_state.into_block().await.unwrap(); crate::db::Db::bootstrap(":memory:".into(), &genesis_block).unwrap(); } diff --git a/crates/store/src/errors.rs b/crates/store/src/errors.rs index 61bbf3e99..0b5b1c354 100644 --- a/crates/store/src/errors.rs +++ b/crates/store/src/errors.rs @@ -17,7 +17,6 @@ use miden_protocol::errors::{ AccountTreeError, AssetError, AssetVaultError, - FeeError, NoteError, NullifierTreeError, StorageMapError, @@ -139,22 +138,6 @@ pub enum StateInitializationError { AccountToDeltaConversionFailed(String), } -#[derive(Debug, Error)] -pub enum GenesisError { - // ERRORS WITH AUTOMATIC CONVERSIONS FROM NESTED ERROR TYPES - // --------------------------------------------------------------------------------------------- - #[error("database error")] - Database(#[from] DatabaseError), - #[error("failed to build genesis account tree")] - AccountTree(#[source] AccountTreeError), - #[error("failed to deserialize genesis file")] - GenesisFileDeserialization(#[from] DeserializationError), - #[error("fee cannot be created")] - Fee(#[from] FeeError), - #[error("failed to build account delta from account")] - AccountDelta(AccountError), -} - // ENDPOINT ERRORS // ================================================================================================= #[derive(Error, Debug)] @@ -582,7 +565,6 @@ mod compile_tests { AccountError, DatabaseError, DeserializationError, - GenesisError, NetworkAccountError, NoteError, RecvError, @@ -613,7 +595,6 @@ mod compile_tests { ensure_is_error::(PhantomData); ensure_is_error::(PhantomData); - ensure_is_error::(PhantomData); ensure_is_error::(PhantomData); ensure_is_error::>(PhantomData); } diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index acdeb304c..926e757b7 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -68,9 +68,9 @@ fn parsing_yields_expected_default_values() -> TestResult { Ok(()) } -#[test] +#[tokio::test] #[miden_node_test_macro::enable_logging] -fn genesis_accounts_have_nonce_one() -> TestResult { +async fn genesis_accounts_have_nonce_one() -> TestResult { let gcfg = GenesisConfig::default(); let (state, secrets) = gcfg.into_state(SecretKey::new()).unwrap(); let mut iter = secrets.as_account_files(&state); @@ -79,7 +79,7 @@ fn genesis_accounts_have_nonce_one() -> TestResult { assert_eq!(status_quo.account.nonce(), ONE); - let _block = state.into_block()?; + let _block = state.into_block().await?; Ok(()) } @@ -288,9 +288,9 @@ path = "does_not_exist.mac" ); } -#[test] +#[tokio::test] #[miden_node_test_macro::enable_logging] -fn parsing_agglayer_sample_with_account_files() -> TestResult { +async fn parsing_agglayer_sample_with_account_files() -> TestResult { use miden_protocol::account::AccountType; // Use the actual sample file path since it references relative .mac files @@ -350,7 +350,7 @@ fn parsing_agglayer_sample_with_account_files() -> TestResult { assert_eq!(secrets.secrets.len(), 1, "Only native faucet should generate a secret"); // Verify the genesis state can be converted to a block - let _block = state.into_block()?; + let _block = state.into_block().await?; Ok(()) } diff --git a/crates/store/src/genesis/mod.rs b/crates/store/src/genesis/mod.rs index 5df1825d6..08d68fe1b 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -1,3 +1,4 @@ +use miden_node_utils::signer::BlockSigner; use miden_protocol::Word; use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::account::{Account, AccountDelta}; @@ -9,17 +10,15 @@ use miden_protocol::block::{ BlockNoteTree, BlockNumber, BlockProof, - BlockSigner, FeeParameters, ProvenBlock, }; use miden_protocol::crypto::merkle::mmr::{Forest, MmrPeaks}; use miden_protocol::crypto::merkle::smt::{LargeSmt, MemoryStorage, Smt}; +use miden_protocol::errors::AccountError; use miden_protocol::note::Nullifier; use miden_protocol::transaction::{OrderedTransactionHeaders, TransactionKernel}; -use crate::errors::GenesisError; - pub mod config; // GENESIS STATE @@ -68,17 +67,14 @@ impl GenesisState { } impl GenesisState { - /// Returns the block header and the account SMT - pub fn into_block(self) -> Result { + /// Returns the block header and the account SMT. + pub async fn into_block(self) -> anyhow::Result { let accounts: Vec = self .accounts .iter() .map(|account| { let account_update_details = if account.id().is_public() { - AccountUpdateDetails::Delta( - AccountDelta::try_from(account.clone()) - .map_err(GenesisError::AccountDelta)?, - ) + AccountUpdateDetails::Delta(AccountDelta::try_from(account.clone())?) } else { AccountUpdateDetails::Private }; @@ -89,7 +85,7 @@ impl GenesisState { account_update_details, )) }) - .collect::, GenesisError>>()?; + .collect::, AccountError>>()?; // Convert account updates to SMT entries using account_id_to_smt_key let smt_entries = accounts.iter().map(|update| { @@ -134,7 +130,10 @@ impl GenesisState { let block_proof = BlockProof::new_dummy(); - let signature = self.block_signer.sign(&header); + // Sign and assert verification for sanity (no mismatch between frontend and backend signing + // impls). + let signature = self.block_signer.sign(&header).await?; + assert!(signature.verify(header.commitment(), &self.block_signer.public_key())); // SAFETY: Header and accounts should be valid by construction. // No notes or nullifiers are created at genesis, which is consistent with the above empty // block note tree root and empty nullifier tree root. diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index 3a284ceff..8c828f116 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -11,8 +11,8 @@ use miden_node_proto_build::{ store_rpc_api_descriptor, }; use miden_node_utils::panic::{CatchPanicLayer, catch_panic_layer_fn}; +use miden_node_utils::signer::BlockSigner; use miden_node_utils::tracing::grpc::grpc_trace_fn; -use miden_protocol::block::BlockSigner; use tokio::net::TcpListener; use tokio::task::JoinSet; use tokio_stream::wrappers::TcpListenerStream; @@ -54,12 +54,13 @@ impl Store { skip_all, err, )] - pub fn bootstrap( + pub async fn bootstrap( genesis: GenesisState, data_directory: &Path, ) -> anyhow::Result<()> { let genesis = genesis .into_block() + .await .context("failed to convert genesis configuration into the genesis block")?; let data_directory = diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 2c5fea6e5..fe251e8c9 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -20,6 +20,7 @@ testing = ["miden-protocol/testing"] [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } bytes = { version = "1.10" } figment = { features = ["env", "toml"], version = "0.10" } http = { workspace = true } diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 530e971e4..9ff016b0b 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -9,6 +9,7 @@ pub mod limiter; pub mod logging; pub mod lru_cache; pub mod panic; +pub mod signer; pub mod tracing; pub trait ErrorReport: std::error::Error { diff --git a/crates/utils/src/signer.rs b/crates/utils/src/signer.rs new file mode 100644 index 000000000..603e6de55 --- /dev/null +++ b/crates/utils/src/signer.rs @@ -0,0 +1,35 @@ +use core::convert::Infallible; +use core::error; + +use miden_protocol::block::BlockHeader; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SecretKey, Signature}; + +// BLOCK SIGNER +// ================================================================================================ + +/// Trait which abstracts the signing of block headers with ECDSA signatures. +/// +/// Production-level implementations will involve some sort of secure remote backend. The trait also +/// allows for testing with local and ephemeral signers. +#[async_trait::async_trait] +pub trait BlockSigner { + type Error: error::Error + Send + Sync + 'static; + async fn sign(&self, header: &BlockHeader) -> Result; + fn public_key(&self) -> PublicKey; +} + +// SECRET KEY BLOCK SIGNER +// ================================================================================================ + +#[async_trait::async_trait] +impl BlockSigner for SecretKey { + type Error = Infallible; + + async fn sign(&self, header: &BlockHeader) -> Result { + Ok(self.sign(header.commitment())) + } + + fn public_key(&self) -> PublicKey { + self.public_key() + } +} diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index 570f2a8d2..210d1dbae 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -18,8 +18,12 @@ workspace = true [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } +aws-config = { version = "1.8.14" } +aws-sdk-kms = { version = "1.100" } diesel = { workspace = true } diesel_migrations = { workspace = true } +k256 = "0.13.4" miden-node-db = { workspace = true } miden-node-proto = { workspace = true } miden-node-proto-build = { features = ["internal"], workspace = true } diff --git a/crates/validator/src/block_validation/mod.rs b/crates/validator/src/block_validation/mod.rs index 954d043b8..97b61fabc 100644 --- a/crates/validator/src/block_validation/mod.rs +++ b/crates/validator/src/block_validation/mod.rs @@ -1,12 +1,12 @@ use miden_node_db::{DatabaseError, Db}; -use miden_protocol::block::{BlockSigner, ProposedBlock}; +use miden_protocol::block::ProposedBlock; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::Signature; use miden_protocol::errors::ProposedBlockError; use miden_protocol::transaction::{TransactionHeader, TransactionId}; use tracing::{info_span, instrument}; -use crate::COMPONENT; use crate::db::find_unvalidated_transactions; +use crate::{COMPONENT, ValidatorSigner}; // BLOCK VALIDATION ERROR // ================================================================================================ @@ -17,6 +17,8 @@ pub enum BlockValidationError { UnvalidatedTransactions(Vec), #[error("failed to build block")] BlockBuildingFailed(#[source] ProposedBlockError), + #[error("failed to sign block: {0}")] + BlockSigningFailed(String), #[error("failed to select transactions")] DatabaseError(#[source] DatabaseError), } @@ -27,9 +29,9 @@ pub enum BlockValidationError { /// Validates a block by checking that all transactions in the proposed block have been processed by /// the validator in the past. #[instrument(target = COMPONENT, skip_all, err)] -pub async fn validate_block( +pub async fn validate_block( proposed_block: ProposedBlock, - signer: &S, + signer: &ValidatorSigner, db: &Db, ) -> Result { // Search for any proposed transactions that have not previously been validated. @@ -53,7 +55,10 @@ pub async fn validate_block( .map_err(BlockValidationError::BlockBuildingFailed)?; // Sign the header. - let signature = info_span!("sign_block").in_scope(|| signer.sign(&header)); + let signature = info_span!("sign_block") + .in_scope(async move || signer.sign(&header).await) + .await + .map_err(|err| BlockValidationError::BlockSigningFailed(err.to_string()))?; Ok(signature) } diff --git a/crates/validator/src/lib.rs b/crates/validator/src/lib.rs index a987304c3..44f883bfc 100644 --- a/crates/validator/src/lib.rs +++ b/crates/validator/src/lib.rs @@ -1,9 +1,11 @@ mod block_validation; mod db; mod server; +mod signers; mod tx_validation; pub use server::Validator; +pub use signers::ValidatorSigner; // CONSTANTS // ================================================================================================= diff --git a/crates/validator/src/server/mod.rs b/crates/validator/src/server/mod.rs index 7f71161a2..ac4b56e51 100644 --- a/crates/validator/src/server/mod.rs +++ b/crates/validator/src/server/mod.rs @@ -12,7 +12,7 @@ use miden_node_utils::ErrorReport; use miden_node_utils::panic::catch_panic_layer_fn; use miden_node_utils::tracing::OpenTelemetrySpanExt; use miden_node_utils::tracing::grpc::grpc_trace_fn; -use miden_protocol::block::{BlockSigner, ProposedBlock}; +use miden_protocol::block::ProposedBlock; use miden_protocol::transaction::{ProvenTransaction, TransactionInputs}; use miden_tx::utils::{Deserializable, Serializable}; use tokio::net::TcpListener; @@ -22,10 +22,10 @@ use tower_http::catch_panic::CatchPanicLayer; use tower_http::trace::TraceLayer; use tracing::{info_span, instrument}; -use crate::COMPONENT; use crate::block_validation::validate_block; use crate::db::{insert_transaction, load}; use crate::tx_validation::validate_transaction; +use crate::{COMPONENT, ValidatorSigner}; // VALIDATOR // ================================================================================ @@ -33,7 +33,7 @@ use crate::tx_validation::validate_transaction; /// The handle into running the gRPC validator server. /// /// Facilitates the running of the gRPC server which implements the validator API. -pub struct Validator { +pub struct Validator { /// The address of the validator component. pub address: SocketAddr, /// Server-side timeout for an individual gRPC request. @@ -42,13 +42,13 @@ pub struct Validator { pub grpc_timeout: Duration, /// The signer used to sign blocks. - pub signer: S, + pub signer: ValidatorSigner, /// The data directory for the validator component's database files. pub data_directory: PathBuf, } -impl Validator { +impl Validator { /// Serves the validator RPC API. /// /// Executes in place (i.e. not spawned) and will run indefinitely until a fatal error is @@ -99,19 +99,19 @@ impl Validator { /// The underlying implementation of the gRPC validator server. /// /// Implements the gRPC API for the validator. -struct ValidatorServer { - signer: S, +struct ValidatorServer { + signer: ValidatorSigner, db: Arc, } -impl ValidatorServer { - fn new(signer: S, db: Db) -> Self { +impl ValidatorServer { + fn new(signer: ValidatorSigner, db: Db) -> Self { Self { signer, db: db.into() } } } #[tonic::async_trait] -impl api_server::Api for ValidatorServer { +impl api_server::Api for ValidatorServer { /// Returns the status of the validator. async fn status( &self, diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs new file mode 100644 index 000000000..c702c117a --- /dev/null +++ b/crates/validator/src/signers/kms.rs @@ -0,0 +1,126 @@ +use anyhow::Context; +use aws_sdk_kms::error::SdkError; +use aws_sdk_kms::operation::sign::SignError; +use aws_sdk_kms::types::SigningAlgorithmSpec; +use k256::PublicKey as K256PublicKey; +use k256::elliptic_curve::sec1::ToEncodedPoint; +use k256::pkcs8::DecodePublicKey as _; +use miden_node_utils::signer::BlockSigner; +use miden_protocol::block::BlockHeader; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; +use miden_protocol::crypto::hash::keccak::Keccak256; +use miden_tx::utils::{Deserializable, DeserializationError, Serializable}; + +// KMS SIGNER ERROR +// ================================================================================================ + +#[derive(Debug, thiserror::Error)] +pub enum KmsSignerError { + /// The KMS backend errored out. + #[error("KMS service failure")] + KmsServiceError(#[source] Box>), + /// The KMS backend did not error but returned an empty signature. + #[error("KMS request returned an empty result")] + EmptyBlob, + /// The KMS backend returned a signature with an invalid format. + #[error("k256 signature error")] + K256Error(#[source] k256::ecdsa::Error), + /// The KMS backend returned a signature with an invalid format. + #[error("invalid signature format")] + SignatureFormatError(#[source] DeserializationError), +} + +// KMS SIGNER +// ================================================================================================ + +/// Block signer that uses AWS KMS to create signatures. +pub struct KmsSigner { + key_id: String, + pub_key: PublicKey, + client: aws_sdk_kms::Client, +} + +impl KmsSigner { + /// Constructs a new KMS signer and retrieves the corresponding public key from the AWS backend. + /// + /// The supplied `key_id` must be a valid AWS KMS key ID in the AWS region corresponding to the + /// typical `AWS_REGION` env var. + /// + /// A policy statement such as the following is required to allow a process on an EC2 instance + /// to use this signer: + /// ```json + /// { + /// "Sid": "AllowEc2RoleUseOfKey", + /// "Effect": "Allow", + /// "Principal": { + /// "AWS": "arn:aws:iam:::role/" + /// }, + /// "Action": [ + /// "kms:Sign", + /// "kms:Verify", + /// "kms:DescribeKey" + /// "kms:GetPublicKey" + /// ], + /// "Resource": "*" + /// }, + /// ``` + pub async fn new(key_id: impl Into) -> anyhow::Result { + let version = aws_config::BehaviorVersion::v2026_01_12(); + let config = aws_config::load_defaults(version).await; + let client = aws_sdk_kms::Client::new(&config); + let key_id = key_id.into(); + + // Retrieve DER-encoded SPKI. + let pub_key_output = client.get_public_key().key_id(key_id.clone()).send().await?; + let spki_der = pub_key_output.public_key().ok_or(KmsSignerError::EmptyBlob)?.as_ref(); + + // Decode the DER-encoded SPKI and compress it. + let kpub = K256PublicKey::from_public_key_der(spki_der) + .context("failed to parse SPKI as secp256k1")?; + let compressed = kpub.to_encoded_point(true); // 33 bytes, 0x02/0x03 || X. + let sec1_compressed = compressed.as_bytes(); + + // Decode the compressed SPKI as a Miden public key. + let pub_key = PublicKey::read_from_bytes(sec1_compressed)?; + Ok(Self { key_id, pub_key, client }) + } +} + +#[async_trait::async_trait] +impl BlockSigner for KmsSigner { + type Error = KmsSignerError; + + async fn sign(&self, header: &BlockHeader) -> Result { + // The Validator produces Ethereum-style ECDSA (secp256k1) signatures over Keccak-256 + // digests. AWS KMS does not support SHA-3 hashing for ECDSA keys + // (ECC_SECG_P256K1 being the corresponding AWS key-spec), so we pre-hash the + // message and pass MessageType::Digest. KMS signs the provided 32-byte digest + // verbatim. + let msg = header.commitment().to_bytes(); + let digest = Keccak256::hash(&msg); + + // Request signature from KMS backend. + let sign_output = self + .client + .sign() + .key_id(&self.key_id) + .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) + .message_type(aws_sdk_kms::types::MessageType::Digest) + .message(digest.to_bytes().into()) + .send() + .await + .map_err(Box::from) + .map_err(KmsSignerError::KmsServiceError)?; + + // Decode DER-encoded signature. + let sig_der = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?; + // Recovery id is not used by verify(pk), so 0 is fine. + let recovery_id = 0; + Signature::from_der(sig_der.as_ref(), recovery_id) + .map_err(KmsSignerError::SignatureFormatError) + } + + fn public_key(&self) -> PublicKey { + self.pub_key.clone() + } +} diff --git a/crates/validator/src/signers/mod.rs b/crates/validator/src/signers/mod.rs new file mode 100644 index 000000000..9656e045c --- /dev/null +++ b/crates/validator/src/signers/mod.rs @@ -0,0 +1,44 @@ +mod kms; +pub use kms::KmsSigner; +use miden_node_utils::signer::BlockSigner; +use miden_protocol::block::BlockHeader; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{SecretKey, Signature}; + +// VALIDATOR SIGNER +// ================================================================================================= + +/// Signer that the Validator uses to sign blocks. +pub enum ValidatorSigner { + Kms(KmsSigner), + Local(SecretKey), +} + +impl ValidatorSigner { + /// Constructs a signer which uses an AWS KMS key for signing. + /// + /// See [`KmsSigner::new`] for details as to env var configuration and AWS IAM policies required + /// to use this functionality. + pub async fn new_kms(key_id: impl Into) -> anyhow::Result { + let kms_signer = KmsSigner::new(key_id).await?; + Ok(Self::Kms(kms_signer)) + } + + /// Constructs a signer which uses a local secret key for signing. + pub fn new_local(secret_key: SecretKey) -> Self { + Self::Local(secret_key) + } + + /// Signs a block header using the configured signer. + pub async fn sign(&self, header: &BlockHeader) -> anyhow::Result { + match self { + Self::Kms(signer) => { + let sig = signer.sign(header).await?; + Ok(sig) + }, + Self::Local(signer) => { + let sig = ::sign(signer, header).await?; + Ok(sig) + }, + } + } +}