From 539e82f08cb5300bb79c83a704068225e5a8c383 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Feb 2026 12:30:27 +1300 Subject: [PATCH 01/31] Add signers bump to 1.91 --- Cargo.lock | 700 +++++++++++++++++++++++++--- Cargo.toml | 2 +- crates/validator/Cargo.toml | 2 + crates/validator/src/lib.rs | 1 + crates/validator/src/signers/kms.rs | 36 ++ crates/validator/src/signers/mod.rs | 1 + rust-toolchain.toml | 2 +- 7 files changed, 686 insertions(+), 58 deletions(-) create mode 100644 crates/validator/src/signers/kms.rs create mode 100644 crates/validator/src/signers/mod.rs diff --git a/Cargo.lock b/Cargo.lock index c8c67c56a0..95e11ae1c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,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" @@ -233,10 +622,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", @@ -264,8 +653,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", @@ -311,6 +700,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" @@ -464,6 +863,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" @@ -1205,6 +1614,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" @@ -1450,6 +1865,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" @@ -1633,6 +2054,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 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.13" @@ -1644,7 +2084,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap 2.13.0", "slab", "tokio", @@ -1756,6 +2196,17 @@ dependencies = [ "windows-link", ] +[[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" @@ -1766,6 +2217,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" @@ -1773,7 +2235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1784,8 +2246,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1807,6 +2269,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" @@ -1817,9 +2303,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", @@ -1830,19 +2316,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", ] @@ -1852,7 +2354,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", @@ -1867,7 +2369,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -1886,14 +2388,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", @@ -2841,7 +3343,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", @@ -2878,7 +3380,7 @@ version = "0.14.0" dependencies = [ "anyhow", "futures", - "http", + "http 1.4.0", "mediatype", "miden-air", "miden-node-proto", @@ -2985,7 +3487,7 @@ dependencies = [ "anyhow", "bytes", "figment", - "http", + "http 1.4.0", "http-body-util", "itertools 0.14.0", "lru 0.16.3", @@ -3012,6 +3514,8 @@ name = "miden-node-validator" version = "0.14.0" dependencies = [ "anyhow", + "aws-config", + "aws-sdk-kms", "miden-node-proto", "miden-node-proto-build", "miden-node-utils", @@ -3109,7 +3613,7 @@ dependencies = [ "axum", "bytes", "clap 4.5.55", - "http", + "http 1.4.0", "humantime", "miden-block-prover", "miden-node-proto", @@ -3682,7 +4186,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", @@ -3728,6 +4232,12 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +[[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" @@ -3869,7 +4379,7 @@ dependencies = [ "cf-rustracing", "cf-rustracing-jaeger", "hex", - "http", + "http 1.4.0", "httparse", "httpdate", "indexmap 1.9.3", @@ -3908,8 +4418,8 @@ dependencies = [ "derivative", "flate2", "futures", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "httparse", "httpdate", "libc", @@ -3930,7 +4440,7 @@ dependencies = [ "serde", "serde_yaml", "sfv", - "socket2", + "socket2 0.6.2", "strum", "strum_macros", "tokio", @@ -3953,7 +4463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "252a16def05c7adbbdda776e87b2be36e9481c8a77249207a2f3b563e8933b35" dependencies = [ "bytes", - "http", + "http 1.4.0", "httparse", "pingora-error", "pingora-http", @@ -3969,7 +4479,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3542fd0fd0a83212882c5066ae739ba51804f20d624ff7e12ec85113c5c89a" dependencies = [ "bytes", - "http", + "http 1.4.0", "pingora-error", ] @@ -4002,7 +4512,7 @@ dependencies = [ "derivative", "fnv", "futures", - "http", + "http 1.4.0", "log", "pingora-core", "pingora-error", @@ -4050,8 +4560,8 @@ dependencies = [ "bytes", "clap 3.2.25", "futures", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "log", "once_cell", "pingora-cache", @@ -4643,6 +5153,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" @@ -4665,12 +5181,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", @@ -4853,17 +5369,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", ] @@ -4889,12 +5418,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", @@ -4963,6 +5503,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" @@ -5274,6 +5824,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" @@ -5662,7 +6222,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -5688,13 +6248,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", ] @@ -5836,20 +6406,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", @@ -5931,8 +6501,8 @@ checksum = "75214f6b6bd28c19aa752ac09fdf0eea546095670906c21fe3940e180a4c43f2" dependencies = [ "base64", "bytes", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project", "tokio-stream", "tonic", @@ -5951,8 +6521,8 @@ dependencies = [ "byteorder", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "httparse", "js-sys", @@ -5994,8 +6564,8 @@ dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "iri-string", "pin-project-lite", @@ -6248,6 +6818,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" @@ -6289,6 +6865,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" @@ -6909,6 +7491,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 = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index db02abc0d1..5825b3fa8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ homepage = "https://miden.xyz" license = "MIT" readme = "README.md" repository = "https://github.com/0xMiden/miden-node" -rust-version = "1.90" +rust-version = "1.91" version = "0.14.0" # Optimize the cryptography for faster tests involving account creation. diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index 6115e7cff3..eceea507dc 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -18,6 +18,8 @@ workspace = true [dependencies] anyhow = { workspace = true } +aws-config = { version = "1.8.14" } +aws-sdk-kms = { version = "1.100" } miden-node-proto = { workspace = true } miden-node-proto-build = { features = ["internal"], workspace = true } miden-node-utils = { features = ["testing"], workspace = true } diff --git a/crates/validator/src/lib.rs b/crates/validator/src/lib.rs index a45112d275..b87895df95 100644 --- a/crates/validator/src/lib.rs +++ b/crates/validator/src/lib.rs @@ -1,5 +1,6 @@ mod block_validation; mod server; +mod signers; mod tx_validation; pub use server::Validator; diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs new file mode 100644 index 0000000000..b45c3d5cb3 --- /dev/null +++ b/crates/validator/src/signers/kms.rs @@ -0,0 +1,36 @@ +use aws_sdk_kms::types::SigningAlgorithmSpec; +use miden_protocol::block::{BlockHeader, BlockSigner}; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; +use miden_tx::utils::Serializable; + +pub struct KmsSigner { + key_id: String, + client: aws_sdk_kms::Client, +} + +impl KmsSigner { + pub async fn new(key_id: impl Into) -> Self { + let version = aws_config::BehaviorVersion::v2026_01_12(); + let config = aws_config::load_defaults(version).await; + let client = aws_sdk_kms::Client::new(&config); + Self { key_id: key_id.into(), client } + } +} + +impl BlockSigner for KmsSigner { + fn sign(&self, header: &BlockHeader) -> Signature { + let s = tokio::runtime::Handle::current().block_on(async { + self.client + .sign() + .key_id(&self.key_id) + .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) + .message(header.commitment().to_bytes().into()) + .send() + .await + }); + } + + fn public_key(&self) -> PublicKey { + self.public_key() + } +} diff --git a/crates/validator/src/signers/mod.rs b/crates/validator/src/signers/mod.rs new file mode 100644 index 0000000000..5be3299a1c --- /dev/null +++ b/crates/validator/src/signers/mod.rs @@ -0,0 +1 @@ +mod kms; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 6744e56e15..d9a424cef9 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.90" +channel = "1.91" components = ["clippy", "rust-src", "rustfmt"] profile = "minimal" targets = ["wasm32-unknown-unknown"] From 51542fadd5af7474d6e0775f24b8e1426da52509 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 16 Feb 2026 15:50:14 +1300 Subject: [PATCH 02/31] Basic impl on current trait --- crates/validator/src/signers/kms.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index b45c3d5cb3..c216f993de 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -1,7 +1,7 @@ use aws_sdk_kms::types::SigningAlgorithmSpec; use miden_protocol::block::{BlockHeader, BlockSigner}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; -use miden_tx::utils::Serializable; +use miden_tx::utils::{Deserializable, Serializable}; pub struct KmsSigner { key_id: String, @@ -19,7 +19,7 @@ impl KmsSigner { impl BlockSigner for KmsSigner { fn sign(&self, header: &BlockHeader) -> Signature { - let s = tokio::runtime::Handle::current().block_on(async { + let sign_output = tokio::runtime::Handle::current().block_on(async { self.client .sign() .key_id(&self.key_id) @@ -27,7 +27,11 @@ impl BlockSigner for KmsSigner { .message(header.commitment().to_bytes().into()) .send() .await + .expect("todo get updated trait from base next") }); + let sig = sign_output.signature().expect("todo get updated trait from base next"); + let sig = sig.clone().into_inner(); // todo no clone? + Signature::read_from_bytes(&sig).expect("todo get updated trait from base next") } fn public_key(&self) -> PublicKey { From 1a787472b8f2d489c9528d06ed985990cabd4d04 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Feb 2026 11:42:02 +1300 Subject: [PATCH 03/31] Add BlockSigner trait and update kms pub key logic --- Cargo.lock | 2 ++ bin/node/src/commands/bundled.rs | 2 +- crates/store/src/accounts/tests.rs | 1 - crates/store/src/errors.rs | 21 ------------ crates/store/src/genesis/mod.rs | 17 ++++------ crates/store/src/server/mod.rs | 2 +- crates/utils/Cargo.toml | 1 + crates/utils/src/lib.rs | 1 + crates/utils/src/signer.rs | 35 ++++++++++++++++++++ crates/validator/Cargo.toml | 1 + crates/validator/src/block_validation/mod.rs | 11 ++++-- crates/validator/src/server/mod.rs | 3 +- crates/validator/src/signers/kms.rs | 26 +++++++++++---- 13 files changed, 80 insertions(+), 43 deletions(-) create mode 100644 crates/utils/src/signer.rs diff --git a/Cargo.lock b/Cargo.lock index 95e11ae1c1..addcc2c9f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3485,6 +3485,7 @@ name = "miden-node-utils" version = "0.14.0" dependencies = [ "anyhow", + "async-trait", "bytes", "figment", "http 1.4.0", @@ -3514,6 +3515,7 @@ name = "miden-node-validator" version = "0.14.0" dependencies = [ "anyhow", + "async-trait", "aws-config", "aws-sdk-kms", "miden-node-proto", diff --git a/bin/node/src/commands/bundled.rs b/bin/node/src/commands/bundled.rs index 8bc38fd07f..c2d543fa04 100644 --- a/bin/node/src/commands/bundled.rs +++ b/bin/node/src/commands/bundled.rs @@ -7,8 +7,8 @@ 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_utils::signer::BlockSigner; use miden_node_validator::Validator; -use miden_protocol::block::BlockSigner; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::Deserializable; use tokio::net::TcpListener; diff --git a/crates/store/src/accounts/tests.rs b/crates/store/src/accounts/tests.rs index 4514f23690..0d800696bb 100644 --- a/crates/store/src/accounts/tests.rs +++ b/crates/store/src/accounts/tests.rs @@ -1,7 +1,6 @@ //! Tests for `AccountTreeWithHistory` #[cfg(test)] -#[expect(clippy::similar_names)] #[expect(clippy::needless_range_loop)] #[expect(clippy::uninlined_format_args)] #[expect(clippy::cast_sign_loss)] diff --git a/crates/store/src/errors.rs b/crates/store/src/errors.rs index cbd98af752..67326e7e69 100644 --- a/crates/store/src/errors.rs +++ b/crates/store/src/errors.rs @@ -19,7 +19,6 @@ use miden_protocol::errors::{ AccountTreeError, AssetError, AssetVaultError, - FeeError, NoteError, NullifierTreeError, StorageMapError, @@ -230,30 +229,12 @@ pub enum DatabaseSetupError { Io(#[from] io::Error), #[error("database error")] Database(#[from] DatabaseError), - #[error("genesis block error")] - GenesisBlock(#[from] GenesisError), #[error("pool build error")] PoolBuild(#[from] deadpool::managed::BuildError), #[error("Setup deadpool connection pool failed")] Pool(#[from] deadpool::managed::PoolError), } -#[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)] @@ -690,7 +671,6 @@ mod compile_tests { DatabaseError, DatabaseSetupError, DeserializationError, - GenesisError, NetworkAccountError, NoteError, RecvError, @@ -722,7 +702,6 @@ mod compile_tests { ensure_is_error::(PhantomData); 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/mod.rs b/crates/store/src/genesis/mod.rs index 5df1825d66..29b0e0fa92 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -1,3 +1,5 @@ +use futures::executor::block_on; +use miden_node_utils::signer::BlockSigner; use miden_protocol::Word; use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::account::{Account, AccountDelta}; @@ -9,17 +11,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 @@ -69,16 +69,13 @@ impl GenesisState { impl GenesisState { /// Returns the block header and the account SMT - pub fn into_block(self) -> Result { + pub 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 +86,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 +131,7 @@ impl GenesisState { let block_proof = BlockProof::new_dummy(); - let signature = self.block_signer.sign(&header); + let signature = block_on(self.block_signer.sign(&header))?; // 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 3a284ceff4..04d995b04b 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; diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 2c5fea6e51..fe251e8c95 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 530e971e49..9ff016b0b7 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 0000000000..603e6de559 --- /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 eceea507dc..926ad68546 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -18,6 +18,7 @@ workspace = true [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } aws-config = { version = "1.8.14" } aws-sdk-kms = { version = "1.100" } miden-node-proto = { workspace = true } diff --git a/crates/validator/src/block_validation/mod.rs b/crates/validator/src/block_validation/mod.rs index c1cab190bd..bfaf0b0526 100644 --- a/crates/validator/src/block_validation/mod.rs +++ b/crates/validator/src/block_validation/mod.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use miden_protocol::block::{BlockNumber, BlockSigner, ProposedBlock}; +use miden_node_utils::ErrorReport; +use miden_node_utils::signer::BlockSigner; +use miden_protocol::block::{BlockNumber, ProposedBlock}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::Signature; use miden_protocol::errors::ProposedBlockError; use miden_protocol::transaction::TransactionId; @@ -17,6 +19,8 @@ pub enum BlockValidationError { TransactionNotValidated(TransactionId, BlockNumber), #[error("failed to build block")] BlockBuildingFailed(#[from] ProposedBlockError), + #[error("failed to sign block: {0}")] + BlockSigningFailed(String), } // BLOCK VALIDATION @@ -53,7 +57,10 @@ pub async fn validate_block( let (header, _) = proposed_block.into_header_and_body()?; // 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.as_report()))?; Ok(signature) } diff --git a/crates/validator/src/server/mod.rs b/crates/validator/src/server/mod.rs index 89d28d25de..bc62b96781 100644 --- a/crates/validator/src/server/mod.rs +++ b/crates/validator/src/server/mod.rs @@ -10,9 +10,10 @@ use miden_node_proto_build::validator_api_descriptor; use miden_node_utils::ErrorReport; use miden_node_utils::lru_cache::LruCache; use miden_node_utils::panic::catch_panic_layer_fn; +use miden_node_utils::signer::BlockSigner; 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, TransactionHeader, diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index c216f993de..0bd824c767 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -1,24 +1,38 @@ use aws_sdk_kms::types::SigningAlgorithmSpec; -use miden_protocol::block::{BlockHeader, BlockSigner}; +use miden_node_utils::signer::BlockSigner; +use miden_protocol::block::BlockHeader; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; use miden_tx::utils::{Deserializable, Serializable}; pub struct KmsSigner { key_id: String, + pub_key: PublicKey, client: aws_sdk_kms::Client, } impl KmsSigner { - pub async fn new(key_id: impl Into) -> Self { + 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); - Self { key_id: key_id.into(), client } + let key_id = key_id.into(); + + // Retrieve public key. + let pub_key_output = client.get_public_key().key_id(key_id.clone()).send().await?; + + if let Some(pub_key) = pub_key_output.public_key() { + let pub_key = PublicKey::read_from_bytes(&pub_key.clone().into_inner())?; + Ok(Self { key_id, pub_key, client }) + } else { + anyhow::bail!("failed to retrieve public key"); + } } } +#[async_trait::async_trait] impl BlockSigner for KmsSigner { - fn sign(&self, header: &BlockHeader) -> Signature { + type Error = aws_sdk_kms::Error; + async fn sign(&self, header: &BlockHeader) -> Result { let sign_output = tokio::runtime::Handle::current().block_on(async { self.client .sign() @@ -31,10 +45,10 @@ impl BlockSigner for KmsSigner { }); let sig = sign_output.signature().expect("todo get updated trait from base next"); let sig = sig.clone().into_inner(); // todo no clone? - Signature::read_from_bytes(&sig).expect("todo get updated trait from base next") + Ok(Signature::read_from_bytes(&sig).expect("todo get updated trait from base next")) } fn public_key(&self) -> PublicKey { - self.public_key() + self.pub_key.clone() } } From bcf8ee6a6f8c52c140a88b1c00c76247ad0b0944 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Feb 2026 11:44:30 +1300 Subject: [PATCH 04/31] Changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 865e3752ed..776d620db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ - Refactored NTX Builder startup and introduced `NtxBuilderConfig` with configurable parameters ([#1610](https://github.com/0xMiden/miden-node/pull/1610)). - Refactored NTX Builder actor state into `AccountDeltaTracker` and `NotePool` for clarity, and added tracing instrumentation to event broadcasting ([#1611](https://github.com/0xMiden/miden-node/pull/1611)). - Add #[track_caller] to tracing/logging helpers ([#1651](https://github.com/0xMiden/miden-node/pull/1651)). -- Improved tracing span fields ([#1650](https://github.com/0xMiden/miden-node/pull/1650)) +- Improved tracing span fields ([#1650](https://github.com/0xMiden/miden-node/pull/1650)). +- Added KMS `BlockSigner` impl and moved `BlockSigner` trait from miden-base ([#1677](https://github.com/0xMiden/miden-node/pull/1677)). ## v0.13.5 (TBD) From 8a510e23035f58095daf11d812e9fa5ac6ad526f Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 17 Feb 2026 14:09:40 +1300 Subject: [PATCH 05/31] Fix merge --- crates/store/Cargo.toml | 35 +++++++++++++++++++---------------- crates/validator/Cargo.toml | 4 ++-- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index d5f50aafa4..77c0d2c1cd 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -15,21 +15,24 @@ version.workspace = true workspace = true [dependencies] -anyhow = { workspace = true } -deadpool = { default-features = false, features = ["managed", "rt_tokio_1"], version = "0.12" } -deadpool-diesel = { features = ["sqlite"], version = "0.6" } -deadpool-sync = { default-features = false, features = ["tracing"], version = "0.1" } -diesel = { features = ["numeric", "sqlite"], version = "2.3" } -diesel_migrations = { features = ["sqlite"], version = "2.3" } -fs-err = { workspace = true } -hex = { version = "0.4" } -indexmap = { workspace = true } -libsqlite3-sys = { workspace = true } -miden-crypto = { features = ["concurrent", "hashmaps"], workspace = true } -miden-node-proto = { workspace = true } -miden-node-proto-build = { features = ["internal"], workspace = true } -miden-node-utils = { workspace = true } -miden-standards = { workspace = true } +anyhow = { workspace = true } +deadpool = { default-features = false, features = ["managed", "rt_tokio_1"], version = "0.12" } +deadpool-diesel = { features = ["sqlite"], version = "0.6" } +deadpool-sync = { default-features = false, features = ["tracing"], version = "0.1" } +diesel = { features = ["numeric", "sqlite"], version = "2.3" } +diesel_migrations = { features = ["sqlite"], version = "2.3" } +fs-err = { workspace = true } +futures = { workspace = true } +hex = { version = "0.4" } +indexmap = { workspace = true } +libsqlite3-sys = { workspace = true } +miden-block-prover = { workspace = true } +miden-crypto = { features = ["concurrent", "hashmaps"], workspace = true } +miden-node-proto = { workspace = true } +miden-node-proto-build = { features = ["internal"], workspace = true } +miden-node-utils = { workspace = true } +miden-remote-prover-client = { workspace = true } +miden-standards = { workspace = true } # TODO remove `testing` from `miden-protocol`, required for `BlockProof::new_dummy` miden-protocol = { features = ["std", "testing"], workspace = true } pretty_assertions = { workspace = true } @@ -63,7 +66,7 @@ termtree = { version = "0.5" } [features] default = ["rocksdb"] -rocksdb = ["miden-crypto/rocksdb", "miden-node-rocksdb-cxx-linkage-fix"] +rocksdb = ["miden-crypto/rocksdb"] [[bench]] harness = false diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index 926ad68546..d6a0217ab6 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -19,8 +19,8 @@ workspace = true [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -aws-config = { version = "1.8.14" } -aws-sdk-kms = { version = "1.100" } +aws-config = { version = "1.8.14" } +aws-sdk-kms = { version = "1.100" } miden-node-proto = { workspace = true } miden-node-proto-build = { features = ["internal"], workspace = true } miden-node-utils = { features = ["testing"], workspace = true } From 1ebd9529f4da95eec55d969d66045f797cc0e2f6 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 10:18:32 +1300 Subject: [PATCH 06/31] command and kms error --- bin/node/src/commands/mod.rs | 1 + bin/node/src/commands/validator.rs | 33 ++++++++++++++++-- crates/validator/src/lib.rs | 1 + crates/validator/src/signers/kms.rs | 54 +++++++++++++++++++++-------- crates/validator/src/signers/mod.rs | 1 + 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/bin/node/src/commands/mod.rs b/bin/node/src/commands/mod.rs index a4c9088460..f2d540d9f3 100644 --- a/bin/node/src/commands/mod.rs +++ b/bin/node/src/commands/mod.rs @@ -40,6 +40,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); diff --git a/bin/node/src/commands/validator.rs b/bin/node/src/commands/validator.rs index 461e446c1a..819e447695 100644 --- a/bin/node/src/commands/validator.rs +++ b/bin/node/src/commands/validator.rs @@ -1,9 +1,11 @@ +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_utils::signer::BlockSigner; +use miden_node_validator::{KmsSigner, Validator}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::Deserializable; use url::Url; @@ -13,6 +15,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, @@ -48,27 +51,51 @@ pub enum ValidatorCommand { /// Insecure, hex-encoded validator secret key for development and testing purposes. /// - /// If not provided, a predefined key is used. + /// Value is ignored if `kms.key-id` is provided. #[arg(long = "key", 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. + #[arg(long = "kms.key-id", env = ENV_VALIDATOR_KMS_KEY_ID, value_name = "VALIDATOR_KMS_KEY_ID")] + 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 = KmsSigner::new(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())?; + Self::serve(address, grpc_timeout, signer, data_directory).await + } + } + /// Runs the validator component until failure. + async fn serve( + address: SocketAddr, + grpc_timeout: Duration, + signer: S, + data_directory: PathBuf, + ) -> anyhow::Result<()> + where + S: BlockSigner + Send + Sync + 'static, + { Validator { address, grpc_timeout, diff --git a/crates/validator/src/lib.rs b/crates/validator/src/lib.rs index 0ad8521fad..f6d9c13dbb 100644 --- a/crates/validator/src/lib.rs +++ b/crates/validator/src/lib.rs @@ -5,6 +5,7 @@ mod signers; mod tx_validation; pub use server::Validator; +pub use signers::KmsSigner; // CONSTANTS // ================================================================================================= diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index 0bd824c767..c835b143c0 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -1,9 +1,31 @@ +use aws_sdk_kms::error::SdkError; +use aws_sdk_kms::operation::sign::SignError; use aws_sdk_kms::types::SigningAlgorithmSpec; use miden_node_utils::signer::BlockSigner; use miden_protocol::block::BlockHeader; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; -use miden_tx::utils::{Deserializable, Serializable}; +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(#[from] Box>), + /// The KMS backend did not error but returned an empty signature. + #[error("signing request returned empty signature")] + EmptySignature, + /// The KMS backend returned a signature with an invalid format. + #[error("signature invalid format")] + SignatureFormatError(#[from] DeserializationError), +} + +// KMS SIGNER +// ================================================================================================ + +/// Block signer that uses AWS KMS to create signatures. pub struct KmsSigner { key_id: String, pub_key: PublicKey, @@ -19,7 +41,6 @@ impl KmsSigner { // Retrieve public key. let pub_key_output = client.get_public_key().key_id(key_id.clone()).send().await?; - if let Some(pub_key) = pub_key_output.public_key() { let pub_key = PublicKey::read_from_bytes(&pub_key.clone().into_inner())?; Ok(Self { key_id, pub_key, client }) @@ -31,21 +52,24 @@ impl KmsSigner { #[async_trait::async_trait] impl BlockSigner for KmsSigner { - type Error = aws_sdk_kms::Error; + type Error = KmsSignerError; + async fn sign(&self, header: &BlockHeader) -> Result { - let sign_output = tokio::runtime::Handle::current().block_on(async { - self.client - .sign() - .key_id(&self.key_id) - .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) - .message(header.commitment().to_bytes().into()) - .send() - .await - .expect("todo get updated trait from base next") - }); - let sig = sign_output.signature().expect("todo get updated trait from base next"); + // Request signature from KMS backend. + let sign_output = self + .client + .sign() + .key_id(&self.key_id) + .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) + .message(header.commitment().to_bytes().into()) + .send() + .await + .map_err(Box::from)?; + + // Handle the returned signature. + let sig = sign_output.signature().ok_or(KmsSignerError::EmptySignature)?; let sig = sig.clone().into_inner(); // todo no clone? - Ok(Signature::read_from_bytes(&sig).expect("todo get updated trait from base next")) + Ok(Signature::read_from_bytes(&sig)?) } fn public_key(&self) -> PublicKey { diff --git a/crates/validator/src/signers/mod.rs b/crates/validator/src/signers/mod.rs index 5be3299a1c..bc4abf6ffb 100644 --- a/crates/validator/src/signers/mod.rs +++ b/crates/validator/src/signers/mod.rs @@ -1 +1,2 @@ mod kms; +pub use kms::KmsSigner; From 1dd7271888b286af90b114cf02b6046627d31d4a Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 10:35:16 +1300 Subject: [PATCH 07/31] RM clones --- crates/validator/src/signers/kms.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index c835b143c0..a0c1ac159c 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -42,7 +42,7 @@ impl KmsSigner { // Retrieve public key. let pub_key_output = client.get_public_key().key_id(key_id.clone()).send().await?; if let Some(pub_key) = pub_key_output.public_key() { - let pub_key = PublicKey::read_from_bytes(&pub_key.clone().into_inner())?; + let pub_key = PublicKey::read_from_bytes(pub_key.as_ref())?; Ok(Self { key_id, pub_key, client }) } else { anyhow::bail!("failed to retrieve public key"); @@ -68,8 +68,7 @@ impl BlockSigner for KmsSigner { // Handle the returned signature. let sig = sign_output.signature().ok_or(KmsSignerError::EmptySignature)?; - let sig = sig.clone().into_inner(); // todo no clone? - Ok(Signature::read_from_bytes(&sig)?) + Ok(Signature::read_from_bytes(sig.as_ref())?) } fn public_key(&self) -> PublicKey { From daaf555239844c29f18e0d379e7af6f264319dc3 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 11:39:05 +1300 Subject: [PATCH 08/31] pub key der decode --- Cargo.lock | 2 ++ crates/validator/Cargo.toml | 2 ++ crates/validator/src/signers/kms.rs | 51 +++++++++++++++++++++++------ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56219d732b..85736a75d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3521,12 +3521,14 @@ dependencies = [ "deadpool-diesel", "diesel", "diesel_migrations", + "k256", "miden-node-proto", "miden-node-proto-build", "miden-node-store", "miden-node-utils", "miden-protocol", "miden-tx", + "spki", "thiserror 2.0.18", "tokio", "tokio-stream", diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index add416af37..dd496fc5ca 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -30,6 +30,8 @@ miden-node-store = { workspace = true } miden-node-utils = { features = ["testing"], workspace = true } miden-protocol = { workspace = true } miden-tx = { workspace = true } +spki = "0.7.3" +k256 = "0.13.4" thiserror = { workspace = true } tokio = { features = ["macros", "net", "rt-multi-thread"], workspace = true } tokio-stream = { features = ["net"], workspace = true } diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index a0c1ac159c..d37cebabd1 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -1,6 +1,7 @@ use aws_sdk_kms::error::SdkError; use aws_sdk_kms::operation::sign::SignError; use aws_sdk_kms::types::SigningAlgorithmSpec; +use k256::elliptic_curve::sec1::ToEncodedPoint; use miden_node_utils::signer::BlockSigner; use miden_protocol::block::BlockHeader; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature}; @@ -15,10 +16,10 @@ pub enum KmsSignerError { #[error("KMS service failure")] KmsServiceError(#[from] Box>), /// The KMS backend did not error but returned an empty signature. - #[error("signing request returned empty signature")] - EmptySignature, + #[error("KMS request returned an empty result")] + EmptyBlob, /// The KMS backend returned a signature with an invalid format. - #[error("signature invalid format")] + #[error("invalid signature format")] SignatureFormatError(#[from] DeserializationError), } @@ -39,14 +40,43 @@ impl KmsSigner { let client = aws_sdk_kms::Client::new(&config); let key_id = key_id.into(); - // Retrieve public key. + // Retrieve DER-encoded SPKI (NOT a certificate). let pub_key_output = client.get_public_key().key_id(key_id.clone()).send().await?; - if let Some(pub_key) = pub_key_output.public_key() { - let pub_key = PublicKey::read_from_bytes(pub_key.as_ref())?; - Ok(Self { key_id, pub_key, client }) - } else { - anyhow::bail!("failed to retrieve public key"); + let spki_der = pub_key_output.public_key().ok_or(KmsSignerError::EmptyBlob)?.as_ref(); + + // Print OIDs for sanity (you already did this) + let spki = spki::SubjectPublicKeyInfoRef::try_from(spki_der)?; + println!("SPKI algorithm OID: {}", spki.algorithm.oid); + if let Some(params) = spki.algorithm.parameters { + println!("SPKI params: {:?}", params); } + + // Use k256 to decode SPKI and re-encode in the shapes Miden may expect + use k256::pkcs8::DecodePublicKey as _; + use k256::{EncodedPoint, PublicKey as K256PublicKey}; + + let kpub = K256PublicKey::from_public_key_der(spki_der) + .map_err(|e| anyhow::anyhow!("failed to parse SPKI as secp256k1: {e}"))?; + let uncompressed = kpub.to_encoded_point(false); // 65 bytes, 0x04 || X || Y + let compressed = kpub.to_encoded_point(true); // 33 bytes, 0x02/0x03 || X + + let sec1_uncompressed = uncompressed.as_bytes(); + let sec1_compressed = compressed.as_bytes(); + let raw_xy = &sec1_uncompressed[1..]; // 64 bytes X||Y + + println!( + "encodings: compressed_len={}, uncompressed_len={}, raw_xy_len={}", + sec1_compressed.len(), + sec1_uncompressed.len(), + raw_xy.len() + ); + + // Try encodings in sensible order: compressed -> uncompressed -> raw XY + let pub_key = PublicKey::read_from_bytes(sec1_compressed) + .or_else(|_| PublicKey::read_from_bytes(sec1_uncompressed)) + .or_else(|_| PublicKey::read_from_bytes(raw_xy))?; + + Ok(Self { key_id, pub_key, client }) } } @@ -61,13 +91,14 @@ impl BlockSigner for KmsSigner { .sign() .key_id(&self.key_id) .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) + .message_type(aws_sdk_kms::types::MessageType::Digest) .message(header.commitment().to_bytes().into()) .send() .await .map_err(Box::from)?; // Handle the returned signature. - let sig = sign_output.signature().ok_or(KmsSignerError::EmptySignature)?; + let sig = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?; Ok(Signature::read_from_bytes(sig.as_ref())?) } From 05628b12d63a3e2513803415fb688c65e77bd0a2 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 11:50:53 +1300 Subject: [PATCH 09/31] update bootstrap --- bin/node/src/commands/bundled.rs | 9 ++++ bin/node/src/commands/store.rs | 80 ++++++++++++++++++++---------- bin/node/src/commands/validator.rs | 2 + 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/bin/node/src/commands/bundled.rs b/bin/node/src/commands/bundled.rs index 795cd6fe5c..8d08563a92 100644 --- a/bin/node/src/commands/bundled.rs +++ b/bin/node/src/commands/bundled.rs @@ -22,6 +22,7 @@ use crate::commands::{ ENV_ENABLE_OTEL, ENV_GENESIS_CONFIG_FILE, ENV_VALIDATOR_KEY, + ENV_VALIDATOR_KMS_KEY_ID, INSECURE_VALIDATOR_KEY_HEX, NtxBuilderConfig, ValidatorConfig, @@ -50,6 +51,8 @@ pub enum BundledCommand { /// Insecure, hex-encoded validator secret key for development and testing purposes. /// /// If not provided, a predefined key is used. + /// + /// Value is ignored if `kms.key-id` is provided. #[arg( long = "validator.key", env = ENV_VALIDATOR_KEY, @@ -57,6 +60,10 @@ pub enum BundledCommand { default_value = INSECURE_VALIDATOR_KEY_HEX )] validator_key: String, + + /// Key ID for the KMS key used by validator to sign blocks. + #[arg(long = "kms.key-id", env = ENV_VALIDATOR_KMS_KEY_ID, value_name = "VALIDATOR_KMS_KEY_ID")] + validator_kms_key_id: Option, }, /// Runs all three node components in the same process. @@ -113,6 +120,7 @@ impl BundledCommand { accounts_directory, genesis_config_file, validator_key, + validator_kms_key_id: kms_key_id, } => { // Currently the bundled bootstrap is identical to the store's bootstrap. crate::commands::store::StoreCommand::Bootstrap { @@ -120,6 +128,7 @@ impl BundledCommand { accounts_directory, genesis_config_file, validator_key, + validator_kms_key_id: kms_key_id, } .handle() .await diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 54c741e4d3..aa8f4af4a2 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -5,6 +5,7 @@ use anyhow::Context; use miden_node_store::Store; use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; use miden_node_utils::grpc::UrlExt; +use miden_node_validator::KmsSigner; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::Deserializable; use url::Url; @@ -21,6 +22,7 @@ use crate::commands::{ ENV_ENABLE_OTEL, ENV_GENESIS_CONFIG_FILE, ENV_VALIDATOR_KEY, + ENV_VALIDATOR_KMS_KEY_ID, INSECURE_VALIDATOR_KEY_HEX, duration_to_human_readable_string, }; @@ -56,6 +58,10 @@ pub enum StoreCommand { default_value = INSECURE_VALIDATOR_KEY_HEX )] validator_key: String, + + /// Key ID for the KMS key used by validator to sign blocks. + #[arg(long = "kms.key-id", env = ENV_VALIDATOR_KMS_KEY_ID, value_name = "VALIDATOR_KMS_KEY_ID")] + validator_kms_key_id: Option, }, /// Starts the store component. @@ -112,12 +118,17 @@ impl StoreCommand { accounts_directory, genesis_config_file, validator_key, - } => Self::bootstrap( - &data_directory, - &accounts_directory, - genesis_config_file.as_ref(), - validator_key, - ), + validator_kms_key_id, + } => { + Self::bootstrap( + &data_directory, + &accounts_directory, + genesis_config_file.as_ref(), + validator_key, + validator_kms_key_id, + ) + .await + }, StoreCommand::Start { rpc_url, ntx_builder_url, @@ -190,15 +201,13 @@ 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_kms_key_id: Option, ) -> 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| { @@ -210,8 +219,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)? { @@ -234,19 +241,42 @@ impl StoreCommand { } } - // Write the accounts to disk - for item in secrets.as_account_files(&genesis_state) { - let AccountFileWithName { account_file, name } = item?; - let accountpath = accounts_directory.join(name); - // do not override existing keys - fs_err::OpenOptions::new() - .create_new(true) - .write(true) - .open(&accountpath) - .context("key file already exists")?; - account_file.write(accountpath)?; - } + if let Some(key_id) = validator_kms_key_id { + // Retrieve the validator key from the KMS. + let signer = KmsSigner::new(key_id).await?; + let (genesis_state, secrets) = config.into_state(signer)?; + // Write the accounts to disk + for item in secrets.as_account_files(&genesis_state) { + let AccountFileWithName { account_file, name } = item?; + let accountpath = accounts_directory.join(name); + // do not override existing keys + fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .open(&accountpath) + .context("key file already exists")?; + account_file.write(accountpath)?; + } - Store::bootstrap(genesis_state, data_directory) + Store::bootstrap(genesis_state, data_directory) + } else { + // Decode the validator key. + let signer = SecretKey::read_from_bytes(&hex::decode(validator_key)?)?; + let (genesis_state, secrets) = config.into_state(signer)?; + // Write the accounts to disk + for item in secrets.as_account_files(&genesis_state) { + let AccountFileWithName { account_file, name } = item?; + let accountpath = accounts_directory.join(name); + // do not override existing keys + fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .open(&accountpath) + .context("key file already exists")?; + account_file.write(accountpath)?; + } + + Store::bootstrap(genesis_state, data_directory) + } } } diff --git a/bin/node/src/commands/validator.rs b/bin/node/src/commands/validator.rs index 819e447695..3142cd74af 100644 --- a/bin/node/src/commands/validator.rs +++ b/bin/node/src/commands/validator.rs @@ -51,6 +51,8 @@ pub enum ValidatorCommand { /// Insecure, hex-encoded validator secret key for development and testing purposes. /// + /// If not provided, a predefined key is used. + /// /// Value is ignored if `kms.key-id` is provided. #[arg(long = "key", env = ENV_VALIDATOR_KEY, value_name = "VALIDATOR_KEY", default_value = INSECURE_VALIDATOR_KEY_HEX)] validator_key: String, From 6b85c15dce795e5591921403a5726d2289a57911 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 12:29:24 +1300 Subject: [PATCH 10/31] e2e working needs cleanup --- crates/store/src/genesis/mod.rs | 3 +++ crates/validator/src/signers/kms.rs | 30 +++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/store/src/genesis/mod.rs b/crates/store/src/genesis/mod.rs index 29b0e0fa92..6fdf23e471 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -131,7 +131,10 @@ impl GenesisState { let block_proof = BlockProof::new_dummy(); + // Sign and assert verification for sanity (no mismatch between frontend and backend signing + // impls). let signature = block_on(self.block_signer.sign(&header))?; + 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/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index d37cebabd1..b553cd5985 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -5,6 +5,7 @@ use k256::elliptic_curve::sec1::ToEncodedPoint; 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 @@ -85,6 +86,9 @@ impl BlockSigner for KmsSigner { type Error = KmsSignerError; async fn sign(&self, header: &BlockHeader) -> Result { + // 1) Compute Keccak-256 over the same bytes Miden signs + let msg = header.commitment().to_bytes(); + let digest = Keccak256::hash(&msg); // 32 bytes // Request signature from KMS backend. let sign_output = self .client @@ -92,14 +96,32 @@ impl BlockSigner for KmsSigner { .key_id(&self.key_id) .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) .message_type(aws_sdk_kms::types::MessageType::Digest) - .message(header.commitment().to_bytes().into()) + .message(digest.to_bytes().into()) .send() .await .map_err(Box::from)?; - // Handle the returned signature. - let sig = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?; - Ok(Signature::read_from_bytes(sig.as_ref())?) + // 3) Convert DER -> 64-byte r||s, and normalize s to low-S + use k256::ecdsa::Signature as K256Signature; + let sig_der = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?; + let ksig = K256Signature::from_der(sig_der.as_ref()).map_err(|e| { + KmsSignerError::SignatureFormatError(DeserializationError::InvalidValue( + format!("DER decode error: {e}").into(), + )) + })?; + let rs = if let Some(norm) = ksig.normalize_s() { + norm.to_bytes() + } else { + ksig.to_bytes() + }; // 64 bytes + + // 3.5) Append a recovery byte `v` to make 65 bytes (r||s||v). + let mut sig65 = [0u8; 65]; + sig65[..64].copy_from_slice(&rs); + sig65[64] = 0; // recovery id; not used by verify(pk), so 0 is fine + + // 4) Parse into Miden signature + Ok(Signature::read_from_bytes(&sig65)?) } fn public_key(&self) -> PublicKey { From 73d52fd248027c539cc2fd74cea88611e554d7f6 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:12:42 +1300 Subject: [PATCH 11/31] Cleanup --- crates/validator/src/signers/kms.rs | 60 +++++++++-------------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index b553cd5985..34aeb6f829 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -1,7 +1,10 @@ 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::ecdsa::Signature as K256Signature; 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}; @@ -20,6 +23,9 @@ pub enum KmsSignerError { #[error("KMS request returned an empty result")] EmptyBlob, /// The KMS backend returned a signature with an invalid format. + #[error("k256 signature error")] + K256Error(#[from] k256::ecdsa::Error), + /// The KMS backend returned a signature with an invalid format. #[error("invalid signature format")] SignatureFormatError(#[from] DeserializationError), } @@ -41,42 +47,18 @@ impl KmsSigner { let client = aws_sdk_kms::Client::new(&config); let key_id = key_id.into(); - // Retrieve DER-encoded SPKI (NOT a certificate). + // 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(); - // Print OIDs for sanity (you already did this) - let spki = spki::SubjectPublicKeyInfoRef::try_from(spki_der)?; - println!("SPKI algorithm OID: {}", spki.algorithm.oid); - if let Some(params) = spki.algorithm.parameters { - println!("SPKI params: {:?}", params); - } - - // Use k256 to decode SPKI and re-encode in the shapes Miden may expect - use k256::pkcs8::DecodePublicKey as _; - use k256::{EncodedPoint, PublicKey as K256PublicKey}; - + // Decode the DER-encoded SPKI and compress it. let kpub = K256PublicKey::from_public_key_der(spki_der) .map_err(|e| anyhow::anyhow!("failed to parse SPKI as secp256k1: {e}"))?; - let uncompressed = kpub.to_encoded_point(false); // 65 bytes, 0x04 || X || Y - let compressed = kpub.to_encoded_point(true); // 33 bytes, 0x02/0x03 || X - - let sec1_uncompressed = uncompressed.as_bytes(); + let compressed = kpub.to_encoded_point(true); // 33 bytes, 0x02/0x03 || X. let sec1_compressed = compressed.as_bytes(); - let raw_xy = &sec1_uncompressed[1..]; // 64 bytes X||Y - - println!( - "encodings: compressed_len={}, uncompressed_len={}, raw_xy_len={}", - sec1_compressed.len(), - sec1_uncompressed.len(), - raw_xy.len() - ); - - // Try encodings in sensible order: compressed -> uncompressed -> raw XY - let pub_key = PublicKey::read_from_bytes(sec1_compressed) - .or_else(|_| PublicKey::read_from_bytes(sec1_uncompressed)) - .or_else(|_| PublicKey::read_from_bytes(raw_xy))?; + // 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 }) } } @@ -86,9 +68,10 @@ impl BlockSigner for KmsSigner { type Error = KmsSignerError; async fn sign(&self, header: &BlockHeader) -> Result { - // 1) Compute Keccak-256 over the same bytes Miden signs + // KMS backend doesn't support Keccak-256 so we do it ourselves. let msg = header.commitment().to_bytes(); - let digest = Keccak256::hash(&msg); // 32 bytes + let digest = Keccak256::hash(&msg); + // Request signature from KMS backend. let sign_output = self .client @@ -101,24 +84,19 @@ impl BlockSigner for KmsSigner { .await .map_err(Box::from)?; - // 3) Convert DER -> 64-byte r||s, and normalize s to low-S - use k256::ecdsa::Signature as K256Signature; + // Convert DER -> 64-byte r||s, and normalize s to low-S. let sig_der = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?; - let ksig = K256Signature::from_der(sig_der.as_ref()).map_err(|e| { - KmsSignerError::SignatureFormatError(DeserializationError::InvalidValue( - format!("DER decode error: {e}").into(), - )) - })?; - let rs = if let Some(norm) = ksig.normalize_s() { + let sig = K256Signature::from_der(sig_der.as_ref())?; + let rs = if let Some(norm) = sig.normalize_s() { norm.to_bytes() } else { - ksig.to_bytes() + sig.to_bytes() }; // 64 bytes // 3.5) Append a recovery byte `v` to make 65 bytes (r||s||v). let mut sig65 = [0u8; 65]; sig65[..64].copy_from_slice(&rs); - sig65[64] = 0; // recovery id; not used by verify(pk), so 0 is fine + sig65[64] = 0; // Recovery id; not used by verify(pk), so 0 is fine. // 4) Parse into Miden signature Ok(Signature::read_from_bytes(&sig65)?) From b7dfece31eea932e57b6d4f600e68611d215778a Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:16:51 +1300 Subject: [PATCH 12/31] More cleanup --- crates/validator/src/signers/kms.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index 34aeb6f829..1c386fcb1f 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -91,14 +91,13 @@ impl BlockSigner for KmsSigner { norm.to_bytes() } else { sig.to_bytes() - }; // 64 bytes + }; // 64 bytes. - // 3.5) Append a recovery byte `v` to make 65 bytes (r||s||v). + // Append a recovery byte `v` to make 65 bytes (r||s||v). let mut sig65 = [0u8; 65]; sig65[..64].copy_from_slice(&rs); - sig65[64] = 0; // Recovery id; not used by verify(pk), so 0 is fine. + sig65[64] = 0; // Recovery id is not used by verify(pk), so 0 is fine. - // 4) Parse into Miden signature Ok(Signature::read_from_bytes(&sig65)?) } From 939ecce2311fdbe531d8c8011227f253138597d7 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:43:34 +1300 Subject: [PATCH 13/31] Lint --- crates/validator/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index dd496fc5ca..8f4584a8f9 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -24,14 +24,14 @@ aws-sdk-kms = { version = "1.100" } deadpool-diesel = { workspace = true } diesel = { workspace = true } diesel_migrations = { workspace = true } +k256 = "0.13.4" miden-node-proto = { workspace = true } miden-node-proto-build = { features = ["internal"], workspace = true } miden-node-store = { workspace = true } miden-node-utils = { features = ["testing"], workspace = true } miden-protocol = { workspace = true } miden-tx = { workspace = true } -spki = "0.7.3" -k256 = "0.13.4" +spki = "0.7.3" thiserror = { workspace = true } tokio = { features = ["macros", "net", "rt-multi-thread"], workspace = true } tokio-stream = { features = ["net"], workspace = true } From 5f493caa3b6d6f84b66e3002e5ef4b4e8b3aabdd Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:44:23 +1300 Subject: [PATCH 14/31] key id env --- bin/node/.env | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/node/.env b/bin/node/.env index 6bdfa9a805..02bceb57e6 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 From fef2deb35d3e7c8cf79fbcde5c4eb703b9f88fd0 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:46:17 +1300 Subject: [PATCH 15/31] Machete --- Cargo.lock | 1 - crates/validator/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85736a75d8..0244644806 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3528,7 +3528,6 @@ dependencies = [ "miden-node-utils", "miden-protocol", "miden-tx", - "spki", "thiserror 2.0.18", "tokio", "tokio-stream", diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index 8f4584a8f9..cdf6a0d278 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -31,7 +31,6 @@ miden-node-store = { workspace = true } miden-node-utils = { features = ["testing"], workspace = true } miden-protocol = { workspace = true } miden-tx = { workspace = true } -spki = "0.7.3" thiserror = { workspace = true } tokio = { features = ["macros", "net", "rt-multi-thread"], workspace = true } tokio-stream = { features = ["net"], workspace = true } From 20467c67cfd65343ec3acc2a1f50a7d55f86371d Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:51:18 +1300 Subject: [PATCH 16/31] Dedupe --- bin/node/src/commands/store.rs | 60 ++++++++++++++++------------------ 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index aa8f4af4a2..436b388e4d 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -5,6 +5,7 @@ use anyhow::Context; use miden_node_store::Store; use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; use miden_node_utils::grpc::UrlExt; +use miden_node_utils::signer::BlockSigner; use miden_node_validator::KmsSigner; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::Deserializable; @@ -241,42 +242,39 @@ impl StoreCommand { } } + // Bootstrap with KMS key or local key. if let Some(key_id) = validator_kms_key_id { - // Retrieve the validator key from the KMS. let signer = KmsSigner::new(key_id).await?; - let (genesis_state, secrets) = config.into_state(signer)?; - // Write the accounts to disk - for item in secrets.as_account_files(&genesis_state) { - let AccountFileWithName { account_file, name } = item?; - let accountpath = accounts_directory.join(name); - // do not override existing keys - fs_err::OpenOptions::new() - .create_new(true) - .write(true) - .open(&accountpath) - .context("key file already exists")?; - account_file.write(accountpath)?; - } - - Store::bootstrap(genesis_state, data_directory) + Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) } else { - // Decode the validator key. let signer = SecretKey::read_from_bytes(&hex::decode(validator_key)?)?; - let (genesis_state, secrets) = config.into_state(signer)?; - // Write the accounts to disk - for item in secrets.as_account_files(&genesis_state) { - let AccountFileWithName { account_file, name } = item?; - let accountpath = accounts_directory.join(name); - // do not override existing keys - fs_err::OpenOptions::new() - .create_new(true) - .write(true) - .open(&accountpath) - .context("key file already exists")?; - account_file.write(accountpath)?; - } + Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) + } + } - Store::bootstrap(genesis_state, data_directory) + fn bootstrap_with_signer( + config: GenesisConfig, + signer: S, + accounts_directory: &Path, + data_directory: &Path, + ) -> anyhow::Result<()> { + // Build genesis state with the provided signer. + let (genesis_state, secrets) = config.into_state(signer)?; + + // Write accouts to file. + for item in secrets.as_account_files(&genesis_state) { + let AccountFileWithName { account_file, name } = item?; + let accountpath = accounts_directory.join(name); + // do not override existing keys + fs_err::OpenOptions::new() + .create_new(true) + .write(true) + .open(&accountpath) + .context("key file already exists")?; + account_file.write(accountpath)?; } + + // Bootstrap store. + Store::bootstrap(genesis_state, data_directory) } } From 548eb0ff69aa5ad6886033e547969f78cbebcb02 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:54:42 +1300 Subject: [PATCH 17/31] typo --- bin/node/src/commands/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 436b388e4d..ff35b81c28 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -261,7 +261,7 @@ impl StoreCommand { // Build genesis state with the provided signer. let (genesis_state, secrets) = config.into_state(signer)?; - // Write accouts to file. + // 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); From 8958797bf2023886d4ab4688e4732faea35701b6 Mon Sep 17 00:00:00 2001 From: sergerad Date: Thu, 19 Feb 2026 13:56:27 +1300 Subject: [PATCH 18/31] Dockerfile --- bin/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/Dockerfile b/bin/node/Dockerfile index 9778daec80..30aef26378 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 && \ From 4e4239bee1ef4280f3f4d33c37fe827cbb4d0976 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 20 Feb 2026 06:44:05 +1300 Subject: [PATCH 19/31] Update comment --- crates/validator/src/signers/kms.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index 1c386fcb1f..f14143217b 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -68,7 +68,7 @@ impl BlockSigner for KmsSigner { type Error = KmsSignerError; async fn sign(&self, header: &BlockHeader) -> Result { - // KMS backend doesn't support Keccak-256 so we do it ourselves. + // AWS KMS does not support SHA3 (Keccak-256), so we need to produce the digest ourselves. let msg = header.commitment().to_bytes(); let digest = Keccak256::hash(&msg); From f36266841436a88663e6dec8e5dd77354b0f7690 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 20 Feb 2026 11:35:59 +1300 Subject: [PATCH 20/31] Add validator signer enum --- bin/node/src/commands/bundled.rs | 34 ++++--------- bin/node/src/commands/mod.rs | 53 ++++++++++++++++++-- bin/node/src/commands/store.rs | 50 ++++++------------ bin/node/src/commands/validator.rs | 34 ++++++++----- crates/block-producer/src/server/tests.rs | 4 +- crates/validator/src/block_validation/mod.rs | 10 ++-- crates/validator/src/lib.rs | 2 +- crates/validator/src/server/mod.rs | 19 ++++--- crates/validator/src/signers/mod.rs | 39 ++++++++++++++ 9 files changed, 152 insertions(+), 93 deletions(-) diff --git a/bin/node/src/commands/bundled.rs b/bin/node/src/commands/bundled.rs index 8d08563a92..c035b2dcc2 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,15 +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, - ENV_VALIDATOR_KMS_KEY_ID, - INSECURE_VALIDATOR_KEY_HEX, NtxBuilderConfig, - ValidatorConfig, + ValidatorKey, duration_to_human_readable_string, }; @@ -48,22 +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. - /// - /// Value is ignored if `kms.key-id` is provided. - #[arg( - long = "validator.key", - 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. - #[arg(long = "kms.key-id", env = ENV_VALIDATOR_KMS_KEY_ID, value_name = "VALIDATOR_KMS_KEY_ID")] - validator_kms_key_id: Option, + /// Configuration for the Validator key used to sign genesis block. + #[command(flatten)] + validator_key: ValidatorKey, }, /// Runs all three node components in the same process. @@ -90,7 +75,7 @@ pub enum BundledCommand { ntx_builder: NtxBuilderConfig, #[command(flatten)] - validator: ValidatorConfig, + validator: BundledValidatorConfig, /// Enables the exporting of traces for OpenTelemetry. /// @@ -120,7 +105,6 @@ impl BundledCommand { accounts_directory, genesis_config_file, validator_key, - validator_kms_key_id: kms_key_id, } => { // Currently the bundled bootstrap is identical to the store's bootstrap. crate::commands::store::StoreCommand::Bootstrap { @@ -128,7 +112,6 @@ impl BundledCommand { accounts_directory, genesis_config_file, validator_key, - validator_kms_key_id: kms_key_id, } .handle() .await @@ -165,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 @@ -321,6 +304,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 f2d540d9f3..0072a42853 100644 --- a/bin/node/src/commands/mod.rs +++ b/bin/node/src/commands/mod.rs @@ -9,6 +9,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; @@ -51,9 +54,53 @@ 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 `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 `key.hex`. + #[arg( + long = "key.kms-id", + env = ENV_VALIDATOR_KMS_KEY_ID, + value_name = "VALIDATOR_KMS_KEY_ID", + )] + validator_kms_key_id: Option, +} + +impl ValidatorKey { + 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 +117,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 ff35b81c28..5cfca40c14 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -6,9 +6,7 @@ use miden_node_store::Store; use miden_node_store::genesis::config::{AccountFileWithName, GenesisConfig}; use miden_node_utils::grpc::UrlExt; use miden_node_utils::signer::BlockSigner; -use miden_node_validator::KmsSigner; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; -use miden_protocol::utils::Deserializable; +use miden_node_validator::ValidatorSigner; use url::Url; use super::{ @@ -22,9 +20,7 @@ use crate::commands::{ ENV_BLOCK_PROVER_URL, ENV_ENABLE_OTEL, ENV_GENESIS_CONFIG_FILE, - ENV_VALIDATOR_KEY, - ENV_VALIDATOR_KMS_KEY_ID, - INSECURE_VALIDATOR_KEY_HEX, + ValidatorKey, duration_to_human_readable_string, }; @@ -47,22 +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, - - /// Key ID for the KMS key used by validator to sign blocks. - #[arg(long = "kms.key-id", env = ENV_VALIDATOR_KMS_KEY_ID, value_name = "VALIDATOR_KMS_KEY_ID")] - validator_kms_key_id: Option, + /// Configuration for the Validator key used to sign genesis block. + #[command(flatten)] + validator_key: ValidatorKey, }, /// Starts the store component. @@ -119,14 +102,12 @@ impl StoreCommand { accounts_directory, genesis_config_file, validator_key, - validator_kms_key_id, } => { Self::bootstrap( &data_directory, &accounts_directory, genesis_config_file.as_ref(), validator_key, - validator_kms_key_id, ) .await }, @@ -206,8 +187,7 @@ impl StoreCommand { data_directory: &Path, accounts_directory: &Path, genesis_config: Option<&PathBuf>, - validator_key: String, - validator_kms_key_id: Option, + validator_key: ValidatorKey, ) -> anyhow::Result<()> { // Parse genesis config (or default if not given). let config = genesis_config @@ -243,18 +223,20 @@ impl StoreCommand { } // Bootstrap with KMS key or local key. - if let Some(key_id) = validator_kms_key_id { - let signer = KmsSigner::new(key_id).await?; - Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) - } else { - let signer = SecretKey::read_from_bytes(&hex::decode(validator_key)?)?; - Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) + let signer = validator_key.into_signer().await?; + match signer { + ValidatorSigner::Kms(signer) => { + Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) + }, + ValidatorSigner::Local(signer) => { + Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) + }, } } - fn bootstrap_with_signer( + fn bootstrap_with_signer( config: GenesisConfig, - signer: S, + signer: impl BlockSigner, accounts_directory: &Path, data_directory: &Path, ) -> anyhow::Result<()> { diff --git a/bin/node/src/commands/validator.rs b/bin/node/src/commands/validator.rs index 3142cd74af..ef3d9363a9 100644 --- a/bin/node/src/commands/validator.rs +++ b/bin/node/src/commands/validator.rs @@ -4,8 +4,7 @@ use std::time::Duration; use anyhow::Context; use miden_node_utils::grpc::UrlExt; -use miden_node_utils::signer::BlockSigner; -use miden_node_validator::{KmsSigner, Validator}; +use miden_node_validator::{Validator, ValidatorSigner}; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; use miden_protocol::utils::Deserializable; use url::Url; @@ -53,12 +52,25 @@ pub enum ValidatorCommand { /// /// If not provided, a predefined key is used. /// - /// Value is ignored if `kms.key-id` is provided. - #[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. - #[arg(long = "kms.key-id", env = ENV_VALIDATOR_KMS_KEY_ID, value_name = "VALIDATOR_KMS_KEY_ID")] + /// + /// 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, }, } @@ -80,24 +92,22 @@ impl ValidatorCommand { // Run validator with KMS key backend if key id provided. if let Some(kms_key_id) = kms_key_id { - let signer = KmsSigner::new(kms_key_id).await?; + 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( + async fn serve( address: SocketAddr, grpc_timeout: Duration, - signer: S, + signer: ValidatorSigner, data_directory: PathBuf, - ) -> anyhow::Result<()> - where - S: BlockSigner + Send + Sync + 'static, - { + ) -> anyhow::Result<()> { Validator { address, grpc_timeout, diff --git a/crates/block-producer/src/server/tests.rs b/crates/block-producer/src/server/tests.rs index 8c98e9da4d..778b742e17 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() diff --git a/crates/validator/src/block_validation/mod.rs b/crates/validator/src/block_validation/mod.rs index bce3fda3c2..9f6773351a 100644 --- a/crates/validator/src/block_validation/mod.rs +++ b/crates/validator/src/block_validation/mod.rs @@ -1,14 +1,12 @@ use miden_node_store::{DatabaseError, Db}; -use miden_node_utils::ErrorReport; -use miden_node_utils::signer::BlockSigner; 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 // ================================================================================================ @@ -31,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. @@ -60,7 +58,7 @@ pub async fn validate_block( let signature = info_span!("sign_block") .in_scope(async move || signer.sign(&header).await) .await - .map_err(|err| BlockValidationError::BlockSigningFailed(err.as_report()))?; + .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 f6d9c13dbb..44f883bfcc 100644 --- a/crates/validator/src/lib.rs +++ b/crates/validator/src/lib.rs @@ -5,7 +5,7 @@ mod signers; mod tx_validation; pub use server::Validator; -pub use signers::KmsSigner; +pub use signers::ValidatorSigner; // CONSTANTS // ================================================================================================= diff --git a/crates/validator/src/server/mod.rs b/crates/validator/src/server/mod.rs index fae654c4f4..fb4c379b4d 100644 --- a/crates/validator/src/server/mod.rs +++ b/crates/validator/src/server/mod.rs @@ -10,7 +10,6 @@ use miden_node_proto_build::validator_api_descriptor; use miden_node_store::Db; use miden_node_utils::ErrorReport; use miden_node_utils::panic::catch_panic_layer_fn; -use miden_node_utils::signer::BlockSigner; use miden_node_utils::tracing::OpenTelemetrySpanExt; use miden_node_utils::tracing::grpc::grpc_trace_fn; use miden_protocol::block::ProposedBlock; @@ -23,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 // ================================================================================ @@ -34,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. @@ -43,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 @@ -100,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/mod.rs b/crates/validator/src/signers/mod.rs index bc4abf6ffb..bc8bc62904 100644 --- a/crates/validator/src/signers/mod.rs +++ b/crates/validator/src/signers/mod.rs @@ -1,2 +1,41 @@ 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. + 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) + }, + } + } +} From 6d96d510f4bf4f3a0c9cf2937fe9dd320dd439b2 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 20 Feb 2026 12:06:15 +1300 Subject: [PATCH 21/31] anyhow context --- crates/validator/src/signers/kms.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index f14143217b..c349d78973 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use aws_sdk_kms::error::SdkError; use aws_sdk_kms::operation::sign::SignError; use aws_sdk_kms::types::SigningAlgorithmSpec; @@ -53,7 +54,7 @@ impl KmsSigner { // Decode the DER-encoded SPKI and compress it. let kpub = K256PublicKey::from_public_key_der(spki_der) - .map_err(|e| anyhow::anyhow!("failed to parse SPKI as secp256k1: {e}"))?; + .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(); From 23aaf307946336db432b5bf5008ac9a532b9c800 Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 20 Feb 2026 12:06:38 +1300 Subject: [PATCH 22/31] fix changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8e1756c8..c11c5060e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ - Refactored NTX Builder startup and introduced `NtxBuilderConfig` with configurable parameters ([#1610](https://github.com/0xMiden/miden-node/pull/1610)). - Refactored NTX Builder actor state into `AccountDeltaTracker` and `NotePool` for clarity, and added tracing instrumentation to event broadcasting ([#1611](https://github.com/0xMiden/miden-node/pull/1611)). - Add #[track_caller] to tracing/logging helpers ([#1651](https://github.com/0xMiden/miden-node/pull/1651)). -- Improved tracing span fields ([#1650](https://github.com/0xMiden/miden-node/pull/1650)). - Added KMS `BlockSigner` impl and moved `BlockSigner` trait from miden-base ([#1677](https://github.com/0xMiden/miden-node/pull/1677)). ## v0.13.5 (TBD) From 77dcb9d0235c9f636500126fd34b2bc30ce022ae Mon Sep 17 00:00:00 2001 From: sergerad Date: Fri, 20 Feb 2026 12:19:43 +1300 Subject: [PATCH 23/31] RM block_on --- bin/node/src/commands/store.rs | 6 ++++-- crates/store/src/db/tests.rs | 24 ++++++++++++------------ crates/store/src/genesis/config/tests.rs | 6 +++--- crates/store/src/genesis/mod.rs | 5 ++--- crates/store/src/server/mod.rs | 3 ++- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 5cfca40c14..4001bebdcd 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -227,14 +227,16 @@ impl StoreCommand { match signer { ValidatorSigner::Kms(signer) => { Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) + .await }, ValidatorSigner::Local(signer) => { Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) + .await }, } } - fn bootstrap_with_signer( + async fn bootstrap_with_signer( config: GenesisConfig, signer: impl BlockSigner, accounts_directory: &Path, @@ -257,6 +259,6 @@ impl StoreCommand { } // Bootstrap store. - Store::bootstrap(genesis_state, data_directory) + Store::bootstrap(genesis_state, data_directory).await } } diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 4c8a9f915d..a56522d476 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/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index 23e2daa43c..fdb3307b2f 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -53,9 +53,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); @@ -64,6 +64,6 @@ 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(()) } diff --git a/crates/store/src/genesis/mod.rs b/crates/store/src/genesis/mod.rs index 6fdf23e471..ac24fe21eb 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -1,4 +1,3 @@ -use futures::executor::block_on; use miden_node_utils::signer::BlockSigner; use miden_protocol::Word; use miden_protocol::account::delta::AccountUpdateDetails; @@ -69,7 +68,7 @@ impl GenesisState { impl GenesisState { /// Returns the block header and the account SMT - pub fn into_block(self) -> anyhow::Result { + pub async fn into_block(self) -> anyhow::Result { let accounts: Vec = self .accounts .iter() @@ -133,7 +132,7 @@ impl GenesisState { // Sign and assert verification for sanity (no mismatch between frontend and backend signing // impls). - let signature = block_on(self.block_signer.sign(&header))?; + 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 diff --git a/crates/store/src/server/mod.rs b/crates/store/src/server/mod.rs index 04d995b04b..8c828f1166 100644 --- a/crates/store/src/server/mod.rs +++ b/crates/store/src/server/mod.rs @@ -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 = From e3075821372f64274fcc6f893e96d7a852db7b33 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 23 Feb 2026 11:32:11 +1300 Subject: [PATCH 24/31] Source not from --- crates/validator/src/block_validation/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/validator/src/block_validation/mod.rs b/crates/validator/src/block_validation/mod.rs index f5f694fd29..97b61fabcb 100644 --- a/crates/validator/src/block_validation/mod.rs +++ b/crates/validator/src/block_validation/mod.rs @@ -16,7 +16,7 @@ pub enum BlockValidationError { #[error("block contains unvalidated transactions {0:?}")] UnvalidatedTransactions(Vec), #[error("failed to build block")] - BlockBuildingFailed(#[from] ProposedBlockError), + BlockBuildingFailed(#[source] ProposedBlockError), #[error("failed to sign block: {0}")] BlockSigningFailed(String), #[error("failed to select transactions")] From f3e0c55fc94a05dbf1c22409b8fa64f2881c727d Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 23 Feb 2026 11:46:09 +1300 Subject: [PATCH 25/31] Update hashing comment --- crates/validator/src/signers/kms.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index c349d78973..55f1dfc20a 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -69,7 +69,10 @@ impl BlockSigner for KmsSigner { type Error = KmsSignerError; async fn sign(&self, header: &BlockHeader) -> Result { - // AWS KMS does not support SHA3 (Keccak-256), so we need to produce the digest ourselves. + // The Validator signs Ethereum-style Keccak-256 digests. AWS KMS does not support SHA-3 + // hashing for ECDSA keys (ECC_SECG_P256K1 being the corresponding 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); From 327275083adffcf97ac82fae70b34f0d1aa11320 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 23 Feb 2026 11:49:26 +1300 Subject: [PATCH 26/31] reword --- crates/validator/src/signers/kms.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index 55f1dfc20a..b85f28a9b9 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -69,9 +69,10 @@ impl BlockSigner for KmsSigner { type Error = KmsSignerError; async fn sign(&self, header: &BlockHeader) -> Result { - // The Validator signs Ethereum-style Keccak-256 digests. AWS KMS does not support SHA-3 - // hashing for ECDSA keys (ECC_SECG_P256K1 being the corresponding key-spec), so we pre-hash - // the message and pass MessageType::Digest. KMS signs the provided 32-byte digest + // 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); From f284675a92648c5cf01f0074e7c2e43e2cba5068 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 23 Feb 2026 11:50:49 +1300 Subject: [PATCH 27/31] Fix flag --- bin/node/src/commands/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/node/src/commands/mod.rs b/bin/node/src/commands/mod.rs index 0072a42853..5505b8ddd0 100644 --- a/bin/node/src/commands/mod.rs +++ b/bin/node/src/commands/mod.rs @@ -64,7 +64,7 @@ pub struct ValidatorKey { /// /// If not provided, a predefined key is used. /// - /// Cannot be used with `key.kms-id`. + /// Cannot be used with `validator.key.kms-id`. #[arg( long = "validator.key.hex", env = ENV_VALIDATOR_KEY, @@ -74,9 +74,9 @@ pub struct ValidatorKey { validator_key: String, /// Key ID for the KMS key used by validator to sign blocks. /// - /// Cannot be used with `key.hex`. + /// Cannot be used with `validator.key.hex`. #[arg( - long = "key.kms-id", + long = "validator.key.kms-id", env = ENV_VALIDATOR_KMS_KEY_ID, value_name = "VALIDATOR_KMS_KEY_ID", )] From 9a27006183ee58d7266672642540f13e22360401 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 23 Feb 2026 12:02:48 +1300 Subject: [PATCH 28/31] Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c11c5060e3..e62802c4b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ - Refactored NTX Builder startup and introduced `NtxBuilderConfig` with configurable parameters ([#1610](https://github.com/0xMiden/miden-node/pull/1610)). - Refactored NTX Builder actor state into `AccountDeltaTracker` and `NotePool` for clarity, and added tracing instrumentation to event broadcasting ([#1611](https://github.com/0xMiden/miden-node/pull/1611)). - Add #[track_caller] to tracing/logging helpers ([#1651](https://github.com/0xMiden/miden-node/pull/1651)). -- Added KMS `BlockSigner` impl and moved `BlockSigner` trait from miden-base ([#1677](https://github.com/0xMiden/miden-node/pull/1677)). +- Added KMS signing support in validator ([#1677](https://github.com/0xMiden/miden-node/pull/1677)). ## v0.13.5 (TBD) From d35c8e432c9ee6bb1ea42784f11583fc103999a4 Mon Sep 17 00:00:00 2001 From: sergerad Date: Mon, 23 Feb 2026 12:08:36 +1300 Subject: [PATCH 29/31] RM more #[from] --- crates/validator/src/signers/kms.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index b85f28a9b9..bf491f1b6c 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -19,16 +19,16 @@ use miden_tx::utils::{Deserializable, DeserializationError, Serializable}; pub enum KmsSignerError { /// The KMS backend errored out. #[error("KMS service failure")] - KmsServiceError(#[from] Box>), + 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(#[from] k256::ecdsa::Error), + K256Error(#[source] k256::ecdsa::Error), /// The KMS backend returned a signature with an invalid format. #[error("invalid signature format")] - SignatureFormatError(#[from] DeserializationError), + SignatureFormatError(#[source] DeserializationError), } // KMS SIGNER @@ -87,11 +87,12 @@ impl BlockSigner for KmsSigner { .message(digest.to_bytes().into()) .send() .await - .map_err(Box::from)?; + .map_err(Box::from) + .map_err(KmsSignerError::KmsServiceError)?; // Convert DER -> 64-byte r||s, and normalize s to low-S. let sig_der = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?; - let sig = K256Signature::from_der(sig_der.as_ref())?; + let sig = K256Signature::from_der(sig_der.as_ref()).map_err(KmsSignerError::K256Error)?; let rs = if let Some(norm) = sig.normalize_s() { norm.to_bytes() } else { @@ -103,7 +104,7 @@ impl BlockSigner for KmsSigner { sig65[..64].copy_from_slice(&rs); sig65[64] = 0; // Recovery id is not used by verify(pk), so 0 is fine. - Ok(Signature::read_from_bytes(&sig65)?) + Ok(Signature::read_from_bytes(&sig65).map_err(KmsSignerError::SignatureFormatError)?) } fn public_key(&self) -> PublicKey { From dfa4712b075114cb3ae3944f8704180623f71c8f Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 24 Feb 2026 14:39:27 +1300 Subject: [PATCH 30/31] Use from_der --- Cargo.lock | 4 ++-- crates/validator/src/signers/kms.rs | 20 +++++--------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64c6a9567b..8af2690ae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2703,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", diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index bf491f1b6c..20c2591387 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -3,7 +3,6 @@ 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::ecdsa::Signature as K256Signature; use k256::elliptic_curve::sec1::ToEncodedPoint; use k256::pkcs8::DecodePublicKey as _; use miden_node_utils::signer::BlockSigner; @@ -90,21 +89,12 @@ impl BlockSigner for KmsSigner { .map_err(Box::from) .map_err(KmsSignerError::KmsServiceError)?; - // Convert DER -> 64-byte r||s, and normalize s to low-S. + // Decode DER-encoded signature. let sig_der = sign_output.signature().ok_or(KmsSignerError::EmptyBlob)?; - let sig = K256Signature::from_der(sig_der.as_ref()).map_err(KmsSignerError::K256Error)?; - let rs = if let Some(norm) = sig.normalize_s() { - norm.to_bytes() - } else { - sig.to_bytes() - }; // 64 bytes. - - // Append a recovery byte `v` to make 65 bytes (r||s||v). - let mut sig65 = [0u8; 65]; - sig65[..64].copy_from_slice(&rs); - sig65[64] = 0; // Recovery id is not used by verify(pk), so 0 is fine. - - Ok(Signature::read_from_bytes(&sig65).map_err(KmsSignerError::SignatureFormatError)?) + // 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 { From 6e4126a938cdbbbfd45c26eaa42995a998735594 Mon Sep 17 00:00:00 2001 From: sergerad Date: Tue, 24 Feb 2026 14:59:38 +1300 Subject: [PATCH 31/31] Improve comments and fix merge --- bin/node/src/commands/mod.rs | 2 ++ bin/node/src/commands/store.rs | 21 ++++++++++++++++----- crates/store/src/genesis/config/tests.rs | 6 +++--- crates/store/src/genesis/mod.rs | 2 +- crates/validator/src/signers/kms.rs | 23 +++++++++++++++++++++++ crates/validator/src/signers/mod.rs | 3 +++ 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/bin/node/src/commands/mod.rs b/bin/node/src/commands/mod.rs index 95439c6e40..b7ef3c3c54 100644 --- a/bin/node/src/commands/mod.rs +++ b/bin/node/src/commands/mod.rs @@ -85,6 +85,8 @@ pub struct ValidatorKey { } 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. diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index b7d98d7d5d..14b266147e 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -225,17 +225,28 @@ impl StoreCommand { let signer = validator_key.into_signer().await?; match signer { ValidatorSigner::Kms(signer) => { - Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) - .await + Self::bootstrap_accounts_and_store( + config, + signer, + accounts_directory, + data_directory, + ) + .await }, ValidatorSigner::Local(signer) => { - Self::bootstrap_with_signer(config, signer, accounts_directory, data_directory) - .await + Self::bootstrap_accounts_and_store( + config, + signer, + accounts_directory, + data_directory, + ) + .await }, } } - async fn bootstrap_with_signer( + /// 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, diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index f58032954e..926e757b73 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -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 ac24fe21eb..08d68fe1b7 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -67,7 +67,7 @@ impl GenesisState { } impl GenesisState { - /// Returns the block header and the account SMT + /// Returns the block header and the account SMT. pub async fn into_block(self) -> anyhow::Result { let accounts: Vec = self .accounts diff --git a/crates/validator/src/signers/kms.rs b/crates/validator/src/signers/kms.rs index 20c2591387..c702c117a4 100644 --- a/crates/validator/src/signers/kms.rs +++ b/crates/validator/src/signers/kms.rs @@ -41,6 +41,29 @@ pub struct KmsSigner { } 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; diff --git a/crates/validator/src/signers/mod.rs b/crates/validator/src/signers/mod.rs index bc8bc62904..9656e045cb 100644 --- a/crates/validator/src/signers/mod.rs +++ b/crates/validator/src/signers/mod.rs @@ -15,6 +15,9 @@ pub enum ValidatorSigner { 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))