diff --git a/Cargo.lock b/Cargo.lock index ed26520..2217971 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -485,6 +485,395 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "aws-arn" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6da0fae6942d45915eebb3c2cfad48c5359d969a58b24696c71baf6a54489a98" +dependencies = [ + "lazy_static", + "regex", + "serde", +] + +[[package]] +name = "aws-config" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455e9fb7743c6f6267eb2830ccc08686fbb3d13c9a689369562fd4d4ef9ea462" +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.3.1", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687bc16bc431a8533fe0097c7f0182874767f920989d7260950172ae8e3c4465" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +dependencies = [ + "bindgen 0.69.5", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6c68419d8ba16d9a7463671593c54f81ba58cab466e9b759418da606dcc2e2" +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", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8565497721d9f18fa29a68bc5d8225b39e1cc7399d7fc6f1ad803ca934341804" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ac1674cba7872061a29baaf02209fefe499ff034dfd91bd4cc59e4d7741489" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6a22f077f5fd3e3c0270d4e1a110346cddf6769e9433eb9e6daceb4ca3b149" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.75.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3258fa707f2f585ee3049d9550954b959002abd59176975150a01d5cf38ae3f" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac 0.12.1", + "http 0.2.12", + "http 1.3.1", + "percent-encoding", + "sha2 0.10.9", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99335bec6cdc50a346fda1437f9fefe33abf8c99060739a546a16457f2862ca9" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f491388e741b7ca73b24130ff464c1478acc34d5b331b7dd0a2ee4643595a15" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.26", + "h2 0.4.10", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.6", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.27", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14302f06d1d5b7d333fd819943075b13d27c7700b414f574c3c35859bfb55d5e" +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.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8531b6d8882fd8f48f82a9754e682e29dd44cff27154af51fa3eb730f59efb" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "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.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a322fec39e4df22777ed3ad8ea868ac2f94cd15e1a55f6ee8d8d6305057689a" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -524,6 +913,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.0" @@ -539,6 +938,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.101", + "which", +] + [[package]] name = "bindgen" version = "0.70.1" @@ -857,6 +1279,16 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[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" version = "0.5.2" @@ -1011,6 +1443,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.3" @@ -1413,6 +1854,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.19" @@ -1657,6 +2104,12 @@ dependencies = [ "percent-encoding", ] +[[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" @@ -1996,6 +2449,15 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "http" version = "0.2.12" @@ -2123,7 +2585,9 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.32", + "log", "rustls 0.21.12", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] @@ -2138,6 +2602,7 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "rustls 0.23.27", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls 0.26.2", @@ -2512,6 +2977,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.172" @@ -2586,6 +3057,12 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -2749,7 +3226,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a3f5406064d310d59b1a219d3c5c9a49caf4047b6496032e3f930876488c34" dependencies = [ - "bindgen", + "bindgen 0.70.1", "cc", "libc", "pkg-config", @@ -3038,6 +3515,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "overload" version = "0.1.1" @@ -3208,6 +3691,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +dependencies = [ + "proc-macro2", + "syn 2.0.101", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3263,6 +3756,10 @@ name = "pythnet-watcher" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", + "aws-arn", + "aws-config", + "aws-sdk-kms", "base64 0.22.1", "borsh 0.9.3", "clap", @@ -3562,6 +4059,12 @@ dependencies = [ "regex-syntax 0.8.5", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -3725,6 +4228,19 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.0.7" @@ -3734,7 +4250,7 @@ dependencies = [ "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -3756,6 +4272,7 @@ version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -3764,6 +4281,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework 2.11.1", +] + [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -3807,7 +4336,7 @@ dependencies = [ "log", "once_cell", "rustls 0.23.27", - "rustls-native-certs", + "rustls-native-certs 0.8.1", "rustls-platform-verifier-android", "rustls-webpki 0.103.3", "security-framework 3.2.0", @@ -3838,6 +4367,7 @@ version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -6869,7 +7399,7 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -7354,6 +7884,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -7372,6 +7908,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" @@ -7396,6 +7942,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "walkdir" version = "2.5.0" @@ -7560,6 +8112,18 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + [[package]] name = "wide" version = "0.7.32" @@ -8036,6 +8600,12 @@ dependencies = [ "time", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "xxhash-rust" version = "0.8.15" diff --git a/Cargo.toml b/Cargo.toml index c484f18..96deb1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" [dependencies] anyhow = "1.0.98" +async-trait = "0.1.88" +aws-arn = "0.3.1" +aws-config = { version = "1.8.0", features = ["behavior-version-latest"] } +aws-sdk-kms = "1.76.0" borsh = "0.9.3" clap = { version = "4.5.39", features = ["derive", "env"] } hex = { version = "0.4.3", features = ["serde"] } diff --git a/README.md b/README.md index 3a349fe..51ffa5d 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,22 @@ Make sure to set `RUST_LOG=INFO` to enable logs from tracing: RUST_LOG=INFO cargo run -- run \ --pythnet-url wss://api2.pythnet.pyth.network \ --server-url https://quorum.pyth.network \ - --secret-key /path/to/secret.key \ + --signer-uri file:///path/to/secret.key \ --wormhole-pid H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU ``` +✅ **Note on `--signer-uri`:** +This argument specifies the signer backend and is compatible with the formats supported by the [Wormhole Guardian Signer](https://github.com/wormhole-foundation/wormhole/blob/main/docs/guardian_signer.md). + +**Supported schemes:** +- `file://` — Load an **armored OpenPGP secp256k1 private key** from a file. +- `amazonkms://` — Use a key stored in AWS KMS. The key must support `ECDSA_SHA_256` and use the `ECC_SECG_P256K1` curve. + +**Example using AWS KMS:** +```bash +--signer-uri amazonkms://arn:aws:kms:us-west-2:123456789012:key/abcde-1234-5678 +``` + --- ### 🌱 Environment Variables (Optional) @@ -42,7 +54,7 @@ Instead of CLI flags, you can also set environment variables: ```bash export PYTHNET_URL=wss://api2.pythnet.pyth.network export SERVER_URL=https://quorum.pyth.network -export SECRET_KEY=/path/to/secret.key +export SIGNER_URI=file:///path/to/secret.key export WORMHOLE_PID=H3fxXJ86ADW2PNuDDmZJg6mzTtPxkYCpNuQUTgmJ7AjU export RUST_LOG=INFO @@ -53,7 +65,7 @@ cargo run ### 🔑 Generate a Secret Key -To generate a new secp256k1 secret key and write it to a file: +To generate a new **armored OpenPGP secp256k1 secret key** and write it to a file: ```bash RUST_LOG=INFO cargo run -- generate-key --output-file .secret diff --git a/src/api_client.rs b/src/api_client.rs index c47b231..db64c0d 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -41,10 +41,11 @@ where } impl Observation

{ - pub fn try_new(body: Body

, signer: impl Signer) -> Result { + pub async fn try_new(body: Body

, signer: Arc) -> Result { let digest = body.digest()?; let signature = signer .sign(digest.secp256k_hash) + .await .map_err(|e| anyhow::anyhow!("Failed to sign observation: {}", e))?; Ok(Self { version: 1, @@ -119,8 +120,8 @@ mod tests { use super::*; - #[test] - fn test_new_signed_observation() { + #[tokio::test] + async fn test_new_signed_observation() { let secret_key = SecretKey::from_byte_array(&[1u8; 32]).expect("Invalid secret key length"); let signer = crate::signer::FileSigner { secret_key }; let body = Body { @@ -132,8 +133,9 @@ mod tests { consistency_level: 1, payload: vec![1, 2, 3, 4, 5], }; - let observation = - Observation::try_new(body.clone(), signer).expect("Failed to create observation"); + let observation = Observation::try_new(body.clone(), Arc::new(signer)) + .await + .expect("Failed to create observation"); assert_eq!(observation.version, 1); assert_eq!(observation.body, body); diff --git a/src/config.rs b/src/config.rs index de0785c..73e63fd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use clap::Parser; +use reqwest::Url; #[derive(clap::ValueEnum, Clone, Debug)] pub enum Mode { @@ -11,9 +12,6 @@ pub struct RunOptions { /// The API key to use for auction server authentication. #[arg(long = "pythnet-url", env = "PYTHNET_URL")] pub pythnet_url: String, - /// Path to the file containing the secret key. - #[arg(long = "secret-key", env = "SECRET_KEY")] - pub secret_key_path: String, /// The Wormhole program ID. #[arg( long = "wormhole-pid", @@ -25,6 +23,10 @@ pub struct RunOptions { pub server_url: String, #[arg(long = "mode", env = "MODE", default_value = "production")] pub mode: Mode, + /// URI for the signer. + /// https://github.com/wormhole-foundation/wormhole/blob/main/docs/guardian_signer.md + #[arg(long = "signer-uri", env = "SIGNER_URI")] + pub signer_uri: Url, } #[derive(Parser, Clone, Debug)] diff --git a/src/main.rs b/src/main.rs index 8aa231b..c1e090f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ use { fs, io::{IsTerminal, Write}, str::FromStr, + sync::Arc, time::Duration, }, tokio::time::sleep, @@ -36,9 +37,9 @@ mod config; mod posted_message; mod signer; -struct RunListenerInput { +struct RunListenerInput { ws_url: String, - signer: T, + signer: Arc, wormhole_pid: Pubkey, accumulator_address: Pubkey, api_client: ApiClient, @@ -115,9 +116,7 @@ fn message_data_to_body(unreliable_data: &PostedMessageUnreliableData) -> Body<& } } -async fn run_listener( - input: RunListenerInput, -) -> Result<(), PubsubClientError> { +async fn run_listener(input: RunListenerInput) -> Result<(), PubsubClientError> { let client = PubsubClient::new(input.ws_url.as_str()).await?; let (mut stream, unsubscribe) = client .program_subscribe( @@ -151,7 +150,7 @@ async fn run_listener( let (api_client, signer) = (input.api_client.clone(), input.signer.clone()); async move { let body = message_data_to_body(&unreliable_data); - match Observation::try_new(body.clone(), signer.clone()) { + match Observation::try_new(body.clone(), signer.clone()).await { Ok(observation) => { if let Err(e) = api_client.post_observation(observation).await { tracing::error!(error = ?e, "Failed to post observation"); @@ -172,8 +171,35 @@ async fn run_listener( )) } +async fn get_signer(run_options: config::RunOptions) -> anyhow::Result> { + match run_options.signer_uri.scheme() { + "file" => { + let signer = signer::FileSigner::try_new( + run_options + .signer_uri + .to_file_path() + .map_err(|_| anyhow::anyhow!("Invalid file path in signer URI"))?, + run_options.mode, + )?; + Ok(Arc::new(signer)) + } + "amazonkms" => { + let arn_string = run_options + .signer_uri + .as_str() + .strip_prefix(&format!("{}://", run_options.signer_uri.scheme())) + .ok_or_else(|| anyhow::anyhow!("Invalid Amazon KMS ARN in signer URI"))?; + let signer = signer::KMSSigner::try_new(arn_string.to_string()).await?; + Ok(Arc::new(signer)) + } + _ => Err(anyhow::anyhow!("Unsupported signer URI scheme")), + } +} + async fn run(run_options: config::RunOptions) { - let signer = signer::FileSigner::try_new(run_options.clone()).expect("Failed to create signer"); + let signer = get_signer(run_options.clone()) + .await + .expect("Failed to create signer"); let client = PubsubClient::new(&run_options.pythnet_url) .await .expect("Invalid WebSocket URL"); @@ -185,7 +211,10 @@ async fn run(run_options: config::RunOptions) { let api_client = ApiClient::try_new(run_options.server_url, None).expect("Failed to create API client"); - let (pubkey, pubkey_evm) = signer.get_public_key().expect("Failed to get public key"); + let (pubkey, pubkey_evm) = signer + .get_public_key() + .await + .expect("Failed to get public key"); let evm_encded_public_key = format!("0x{}", hex::encode(pubkey_evm)); tracing::info!( public_key = ?pubkey, @@ -239,7 +268,10 @@ async fn main() { // Generate keypair (secret + public key) let (secret_key, _) = secp.generate_keypair(&mut rng); let signer = signer::FileSigner { secret_key }; - let (pubkey, pubkey_evm) = signer.get_public_key().expect("Failed to get public key"); + let (pubkey, pubkey_evm) = signer + .get_public_key() + .await + .expect("Failed to get public key"); let guardian_key = GuardianKey { data: secret_key.secret_bytes().to_vec(), @@ -459,8 +491,8 @@ mod tests { assert_eq!(result.unwrap_err().to_string(), INVALID_ACCUMULATOR_ADDRESS); } - #[test] - fn test_parse_and_verify_proto_guardian_key() { + #[tokio::test] + async fn test_parse_and_verify_proto_guardian_key() { // The content below is generated by keygen script at: // https://github.com/wormhole-foundation/wormhole/blob/main/node/cmd/guardiand/keygen.go let content = "-----BEGIN WORMHOLE GUARDIAN PRIVATE KEY----- @@ -485,7 +517,13 @@ mod tests { "f2f3127bff540c8441f99763f586858ef340c9962ad62b6181cd77203e81808f", ); assert_eq!( - hex::encode(signer.get_public_key().expect("Failed to get public key").1), + hex::encode( + signer + .get_public_key() + .await + .expect("Failed to get public key") + .1 + ), "30e41be3f10d3ac813f91e49e189bbb948d030be", ); } diff --git a/src/signer.rs b/src/signer.rs index 7926072..b64120f 100644 --- a/src/signer.rs +++ b/src/signer.rs @@ -1,19 +1,20 @@ use std::{ fs, io::{Cursor, Read}, + path::PathBuf, + str::FromStr, }; +use async_trait::async_trait; use prost::Message as ProstMessage; use secp256k1::{Message, PublicKey, Secp256k1, SecretKey}; use sequoia_openpgp::armor::{Kind, Reader, ReaderMode}; use sha3::{Digest, Keccak256}; -use crate::config::RunOptions; - -pub trait Signer: Send + Sync + Sized + Clone { - fn try_new(run_options: RunOptions) -> anyhow::Result; - fn sign(&self, data: [u8; 32]) -> anyhow::Result<[u8; 65]>; - fn get_public_key(&self) -> anyhow::Result<(PublicKey, [u8; 20])>; +#[async_trait] +pub trait Signer: Send + Sync { + async fn sign(&self, data: [u8; 32]) -> anyhow::Result<[u8; 65]>; + async fn get_public_key(&self) -> anyhow::Result<(PublicKey, [u8; 20])>; } #[derive(Clone, Debug)] @@ -33,6 +34,16 @@ pub const GUARDIAN_KEY_ARMORED_BLOCK: &str = "WORMHOLE GUARDIAN PRIVATE KEY"; pub const STANDARD_ARMOR_LINE_HEADER: &str = "PGP PRIVATE KEY BLOCK"; impl FileSigner { + pub fn try_new(secret_key_path: PathBuf, mode: crate::config::Mode) -> anyhow::Result { + let content = fs::read_to_string(secret_key_path) + .map_err(|e| anyhow::anyhow!("Failed to read secret key file: {}", e))?; + let guardian_key = Self::parse_and_verify_proto_guardian_key(content, mode)?; + Ok(FileSigner { + secret_key: SecretKey::from_slice(&guardian_key.data) + .map_err(|e| anyhow::anyhow!("Failed to create SecretKey: {}", e))?, + }) + } + pub fn parse_and_verify_proto_guardian_key( content: String, mode: crate::config::Mode, @@ -62,18 +73,20 @@ impl FileSigner { } } -impl Signer for FileSigner { - fn try_new(run_options: RunOptions) -> anyhow::Result { - let content = fs::read_to_string(run_options.secret_key_path) - .map_err(|e| anyhow::anyhow!("Failed to read secret key file: {}", e))?; - let guardian_key = Self::parse_and_verify_proto_guardian_key(content, run_options.mode)?; - Ok(FileSigner { - secret_key: SecretKey::from_slice(&guardian_key.data) - .map_err(|e| anyhow::anyhow!("Failed to create SecretKey: {}", e))?, - }) - } +fn get_evm_public_key(public_key: &PublicKey) -> anyhow::Result<[u8; 20]> { + let pubkey_uncompressed = public_key.serialize_uncompressed(); + let pubkey_hash: [u8; 32] = Keccak256::new_with_prefix(&pubkey_uncompressed[1..]) + .finalize() + .into(); + let pubkey_evm: [u8; 20] = pubkey_hash[pubkey_hash.len() - 20..] + .try_into() + .map_err(|e| anyhow::anyhow!("Failed to convert public key hash to EVM format: {}", e))?; + Ok(pubkey_evm) +} - fn sign(&self, data: [u8; 32]) -> anyhow::Result<[u8; 65]> { +#[async_trait] +impl Signer for FileSigner { + async fn sign(&self, data: [u8; 32]) -> anyhow::Result<[u8; 65]> { let signature = Secp256k1::new().sign_ecdsa_recoverable(&Message::from_digest(data), &self.secret_key); let (recovery_id, signature_bytes) = signature.serialize_compact(); @@ -84,19 +97,67 @@ impl Signer for FileSigner { Ok(signature) } - fn get_public_key(&self) -> anyhow::Result<(PublicKey, [u8; 20])> { + async fn get_public_key(&self) -> anyhow::Result<(PublicKey, [u8; 20])> { let secp = Secp256k1::new(); let public_key = self.secret_key.public_key(&secp); - let pubkey_uncompressed = public_key.serialize_uncompressed(); - let pubkey_hash: [u8; 32] = Keccak256::new_with_prefix(&pubkey_uncompressed[1..]) - .finalize() - .into(); - let pubkey_evm: [u8; 20] = - pubkey_hash[pubkey_hash.len() - 20..] - .try_into() - .map_err(|e| { - anyhow::anyhow!("Failed to convert public key hash to EVM format: {}", e) - })?; + let pubkey_evm = get_evm_public_key(&public_key)?; + Ok((public_key, pubkey_evm)) + } +} + +#[derive(Clone, Debug)] +pub struct KMSSigner { + client: aws_sdk_kms::Client, + arn: aws_arn::ResourceName, +} + +impl KMSSigner { + pub async fn try_new(arn_string: String) -> anyhow::Result { + let config = aws_config::load_from_env().await; + let client = aws_sdk_kms::Client::new(&config); + let arn = aws_arn::ResourceName::from_str(&arn_string)?; + Ok(KMSSigner { client, arn }) + } +} + +#[async_trait] +impl Signer for KMSSigner { + async fn sign(&self, data: [u8; 32]) -> anyhow::Result<[u8; 65]> { + let result = self + .client + .sign() + .key_id(self.arn.to_string()) + .message(data.to_vec().into()) + .signing_algorithm(aws_sdk_kms::types::SigningAlgorithmSpec::EcdsaSha256) + .send() + .await + .map_err(|e| anyhow::anyhow!("Failed to sign data with KMS: {}", e))?; + result + .signature + .ok_or_else(|| anyhow::anyhow!("KMS did not return a signature")) + .and_then(|sig| { + let sig = sig.into_inner(); + let signature: [u8; 65] = sig + .try_into() + .map_err(|e| anyhow::anyhow!("Failed to convert KMS signature: {:?}", e))?; + Ok(signature) + }) + } + + async fn get_public_key(&self) -> anyhow::Result<(PublicKey, [u8; 20])> { + let result = self + .client + .get_public_key() + .key_id(self.arn.to_string()) + .send() + .await + .map_err(|e| anyhow::anyhow!("Failed to get public key from KMS: {}", e))?; + let public_key = result + .public_key + .ok_or(anyhow::anyhow!("KMS did not return a public key"))?; + let public_key = PublicKey::from_slice(public_key.as_ref()) + .map_err(|e| anyhow::anyhow!("Failed to create PublicKey from KMS: {}", e))?; + let pubkey_evm = get_evm_public_key(&public_key)?; Ok((public_key, pubkey_evm)) } }