diff --git a/Cargo.lock b/Cargo.lock index 4949c91c..25f9f780 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,7 +101,7 @@ dependencies = [ "k256", "once_cell", "rand 0.8.5", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_with", @@ -1004,6 +1004,19 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "bip39" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "rand_core 0.6.4", + "serde", + "unicode-normalization", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -1122,6 +1135,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "blstrs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" +dependencies = [ + "blst", + "byte-slice-cast", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", +] + [[package]] name = "bollard" version = "0.18.1" @@ -1214,6 +1243,21 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.12.1" @@ -1563,6 +1607,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "convert_case" version = "0.6.0" @@ -1744,6 +1794,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "crypto-mac" version = "0.11.0" @@ -2006,6 +2066,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.111", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -2329,6 +2402,9 @@ name = "dstack-kms" version = "0.5.6" dependencies = [ "anyhow", + "base64 0.22.1", + "blstrs", + "bs58 0.5.1", "chrono", "clap", "dstack-guest-agent-rpc", @@ -2336,17 +2412,22 @@ dependencies = [ "dstack-mr", "dstack-types", "dstack-verifier", + "elliptic-curve", "fs-err", "git-version", "hex", "hex_fmt", + "hkdf", "http-client", "k256", "load_config", + "near-api", + "near-crypto", "parity-scale-codec", "ra-rpc", "ra-tls", "rand 0.8.5", + "rand_core 0.6.4", "reqwest", "ring", "rocket", @@ -2889,6 +2970,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] @@ -2932,6 +3014,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "static_assertions", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -2990,6 +3081,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -3248,7 +3354,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", + "rand 0.8.5", "rand_core 0.6.4", + "rand_xorshift 0.3.0", "subtle", ] @@ -3373,6 +3481,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-conservative" @@ -3501,13 +3612,23 @@ dependencies = [ "hmac 0.12.1", ] +[[package]] +name = "hmac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +dependencies = [ + "crypto-mac 0.9.1", + "digest 0.9.0", +] + [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac", + "crypto-mac 0.11.0", "digest 0.9.0", ] @@ -3693,6 +3814,22 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.19" @@ -3712,9 +3849,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.1", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -4127,6 +4266,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json_comments" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105" + [[package]] name = "k256" version = "0.13.4" @@ -4530,6 +4675,203 @@ dependencies = [ "serde", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "near-abi" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe0c8cdaf8369d8c78f2577d95673007c7f256c85752a66672be5244e2681a6" +dependencies = [ + "borsh", + "schemars 0.8.22", + "semver 1.0.27", + "serde", +] + +[[package]] +name = "near-account-id" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975bb8e272af403d97656893f71e095e1b178ccee571b3ec4a193152be0248f5" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-account-id" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f75ff8eee73815c247d0e17f3c0b705f0e993922a5548acd2ad377aeb67fca" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-api" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321d2778c3cfb314cf6410376788a17b5c34f6154ee61bcd1579748edda454bb" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bip39", + "borsh", + "futures", + "near-api-types", + "near-openapi-client", + "openssl", + "reqwest", + "serde", + "serde_dbgfmt", + "serde_json", + "slipped10", + "thiserror 2.0.17", + "tokio", + "tracing", + "url", + "zstd", +] + +[[package]] +name = "near-api-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4740cd9996fda91dd1cc9034528f0e987ac2c2266f15c6459df7a595e11eb71" +dependencies = [ + "base64 0.22.1", + "borsh", + "bs58 0.5.1", + "ed25519-dalek", + "near-abi", + "near-account-id 2.5.0", + "near-gas", + "near-openapi-types", + "near-token", + "primitive-types 0.10.1", + "secp256k1 0.27.0", + "serde", + "serde_json", + "serde_with", + "sha2 0.10.9", + "thiserror 2.0.17", +] + +[[package]] +name = "near-config-utils" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96c1682d13e9a8a62ea696395bf17afc4ed4b60535223251168217098c27a50" +dependencies = [ + "anyhow", + "json_comments", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "near-crypto" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907fdcefa3a42976cd6a8bf626fe2a87eb0d3b3ff144adc67cf32d53c9494b32" +dependencies = [ + "blake2", + "borsh", + "bs58 0.4.0", + "curve25519-dalek", + "derive_more 0.99.20", + "ed25519-dalek", + "hex", + "near-account-id 1.1.4", + "near-config-utils", + "near-stdx", + "once_cell", + "primitive-types 0.10.1", + "rand 0.8.5", + "secp256k1 0.27.0", + "serde", + "serde_json", + "subtle", + "thiserror 1.0.69", +] + +[[package]] +name = "near-gas" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cecd5d9463587f34f2b7ff4c7104297483a543be8a68a4a04a8ac96c419d1a1" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-openapi-client" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6d2a3dd4e669e14cdd0748b449d7539d0b0fefba17ed8e2d1404a0826908630" +dependencies = [ + "bytes", + "chrono", + "futures-core", + "near-openapi-types", + "progenitor-client", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", +] + +[[package]] +name = "near-openapi-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0c9eaf432ddedd9409d6b1477b49e94679c077adbadce173292f759454afc7" +dependencies = [ + "bs58 0.5.1", + "chrono", + "near-account-id 2.5.0", + "near-gas", + "near-token", + "serde", + "serde_json", + "strum_macros", + "thiserror 2.0.17", +] + +[[package]] +name = "near-stdx" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d5c43f6181873287ddaa25edcc2943d0f2d5da9588231516f2ed0549db1fbac" + +[[package]] +name = "near-token" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34de6b54d82d0790b2a56b677e7b4ecb7f021a7e8559f8611065c890d56cfcda" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "netlink-packet-core" version = "0.7.0" @@ -4850,12 +5192,60 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-src" +version = "300.5.4+3.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + [[package]] name = "optfield" version = "0.4.0" @@ -4913,6 +5303,15 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + [[package]] name = "parcelona" version = "0.4.3" @@ -5041,7 +5440,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ - "crypto-mac", + "crypto-mac 0.11.0", ] [[package]] @@ -5217,6 +5616,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "poly1305" version = "0.8.0" @@ -5289,13 +5694,23 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "primitive-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +dependencies = [ + "fixed-hash 0.7.0", + "uint", +] + [[package]] name = "primitive-types" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ - "fixed-hash", + "fixed-hash 0.8.0", "impl-codec", "uint", ] @@ -5377,6 +5792,21 @@ dependencies = [ "yansi", ] +[[package]] +name = "progenitor-client" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71a0beb939758f229cbae70a4889c7c76a4ac0e90f0b1e7ae9b4636a927d1018" +dependencies = [ + "bytes", + "futures-core", + "percent-encoding", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", +] + [[package]] name = "proptest" version = "1.9.0" @@ -5389,7 +5819,7 @@ dependencies = [ "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", - "rand_xorshift", + "rand_xorshift 0.4.0", "regex-syntax", "rusty-fork", "tempfile", @@ -5792,6 +6222,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -5911,16 +6350,19 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "hickory-resolver 0.25.2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -5932,13 +6374,16 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", ] @@ -6195,7 +6640,7 @@ dependencies = [ "num-integer", "num-traits", "parity-scale-codec", - "primitive-types", + "primitive-types 0.12.2", "proptest", "rand 0.8.5", "rand 0.9.2", @@ -6314,7 +6759,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.5.1", ] [[package]] @@ -6572,6 +7017,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "0.9.0" @@ -6596,6 +7053,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.111", +] + [[package]] name = "schnorrkel" version = "0.11.5" @@ -6651,6 +7120,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys 0.8.2", +] + [[package]] name = "secp256k1" version = "0.30.0" @@ -6659,10 +7138,19 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4473013577ec77b4ee3668179ef1186df3146e2cf2d927bd200974c6fe60fd99" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -6681,6 +7169,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.5.1" @@ -6784,6 +7285,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_dbgfmt" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a8a448a7c23464ff14a8e9a31594ca91ea04fc36314302f3c7acb27717897c" +dependencies = [ + "serde", + "unicode-ident", +] + [[package]] name = "serde_derive" version = "1.0.228" @@ -6795,6 +7306,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "serde_ini" version = "0.2.0" @@ -7070,6 +7592,17 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "slipped10" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a45443e66aa5d96db5e02d17db056e1ca970232a4fe73e1f9bc1816d68f4e98" +dependencies = [ + "ed25519-dalek", + "hmac 0.9.0", + "sha2 0.9.9", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -7588,6 +8121,16 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -7890,6 +8433,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -7971,6 +8523,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -8109,6 +8667,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.83" @@ -8311,6 +8882,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -8858,3 +9440,31 @@ name = "zmij" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/kms/Cargo.toml b/kms/Cargo.toml index bc33bc6a..14936904 100644 --- a/kms/Cargo.toml +++ b/kms/Cargo.toml @@ -47,6 +47,18 @@ tempfile.workspace = true serde-duration.workspace = true dstack-verifier = { workspace = true, default-features = false } dstack-mr.workspace = true +base64.workspace = true + +# BLS12-381 for MPC key derivation +blstrs = "0.7" +elliptic-curve = "0.13" +hkdf = "0.12.4" +bs58 = "0.5" +rand_core = { version = "0.6", features = ["getrandom"] } + +# NEAR integration +near-api = "0.8" +near-crypto = "0.26" # Still needed for InMemorySigner in onboard_service.rs [features] default = [] diff --git a/kms/README.md b/kms/README.md index 08d05b1c..00b063a4 100644 --- a/kms/README.md +++ b/kms/README.md @@ -33,6 +33,9 @@ CVMs running in dstack support three boot modes: ## KMS Implementation ### Components + +#### Ethereum/Base/Phala KMS + 1. **dstack-kms** - Main RPC service for app key requests - Quote verification and boot info validation @@ -56,11 +59,49 @@ CVMs running in dstack support three boot modes: - Controls permissions for individual apps - Maintains the allowed compose hashes for each app +#### NEAR KMS + +1. **dstack-kms** (same as above) + - Can be configured to use NEAR auth API instead of Ethereum + +2. **dstack-kms-auth-near** + - NEAR Protocol chain interface for permission checks + - Two-step validation (same as auth-eth): + 1. KMS control contract check + 2. App control contract check + - Handles hex → NEAR AccountId conversion + - See [auth-near README](auth-near/README.md) for details + +3. **NEAR Authorization Contracts** + - `near-dstack-kms` (Rust contract) + - Maintains a registry for all Applications + - Maintains the allowed KMS Instance MRs + - Maintains the allowed OS Images + - Supports MPC-based root key derivation from NEAR MPC network + - Factory method `register_app()` for deploying app contracts + - `near-dstack-app` (Rust contract) + - Deployed as subaccounts: `{app_id}.{kms_contract_id}` + - Controls permissions for individual apps + - Maintains the allowed compose hashes and device IDs per app + +4. **MPC Key Derivation** (NEAR-specific) + - KMS can derive deterministic root keys from NEAR MPC network + - Uses BLS12-381 cryptography for secure key derivation + - All KMS instances derive the same keys when using the same MPC domain + - Falls back to local key generation if MPC config is incomplete + ### Deployment -The first two components are deployed as an dstack app on dstack in Local-Key-Provider mode. + +The KMS components are deployed as a dstack app on dstack in Local-Key-Provider mode. The docker compose file would look like [this](dstack-app/docker-compose.yaml). -The solidity contracts are deployed on an ethereum compatible chain. +**Authorization Contracts:** +- **Ethereum/Base/Phala**: Solidity contracts deployed on an Ethereum-compatible chain +- **NEAR**: Rust contracts deployed on NEAR Protocol (see [near-kms README](../near-kms/README.md)) + +**App Contract Deployment:** +- **Ethereum**: Use Hardhat tasks (`app:deploy`, `app:add-hash`) or factory method `deployAndRegisterApp()` +- **NEAR**: Use CLI commands (`bun run app:deploy`, `bun run app:add-hash`) or KMS contract's `register_app()` method ## Trustness @@ -89,12 +130,25 @@ On startup, the KMS node will either: - Onboard: Obtain root keys from an existing KMS instance #### Bootstrapping + +**Ethereum/Base/Phala KMS:** During bootstrapping, the KMS node generates two root keys: 1. CA root key: Used to issue x509 certificates for Apps, enabling HTTPS traffic 2. K256 root key: Used to derive Ethereum-compatible keys for Apps After generating the root keys, their public portions can be obtained along with the corresponding TDX quote and registered in the DstackKms contract. +**NEAR KMS with MPC:** +When configured with NEAR MPC, the KMS node derives deterministic root keys from the NEAR MPC network: +1. Generates ephemeral BLS12-381 G1 keypair +2. Calls MPC contract's `request_app_private_key()` with derivation path "kms-root-key" +3. Receives encrypted response and decrypts using ephemeral private key +4. Verifies MPC signature using BLS pairing +5. Derives 32-byte root key using HKDF +6. Converts root key to CA, tmp CA, RPC, and K256 keys using deterministic key derivation + +All KMS instances using the same MPC domain will derive identical root keys, enabling seamless replication without key transfer. If MPC configuration is incomplete, the system automatically falls back to local key generation (same as Ethereum). + #### KMS Self Replication When deploying a new KMS instance (`B`) using an existing instance (`A`), the process follows these steps: @@ -117,9 +171,14 @@ Once onboarded, the KMS node begins listening for app key provisioning requests. When a KMS node receives a key provisioning request, it: 1. Validates the TDX quote of the requesting App -2. Queries the DstackKms contract for provisioning allowance +2. Queries the authorization contract (DstackKms or near-dstack-kms) for provisioning allowance 3. If allowed, generates and sends the keys to the App +**NEAR-specific notes:** +- App contracts are deployed as subaccounts: `{app_id}.{kms_contract_id}` +- Compose hashes are stored as hex strings (without `0x` prefix) +- Use `auth-near` CLI commands for app deployment and compose hash management (see [auth-near README](auth-near/README.md#cli-commands)) + ### Attestation #### Vanilla TDX Quote attestation diff --git a/kms/auth-near/COMPATIBILITY.md b/kms/auth-near/COMPATIBILITY.md new file mode 100644 index 00000000..554e9ebc --- /dev/null +++ b/kms/auth-near/COMPATIBILITY.md @@ -0,0 +1,32 @@ +# NEAR Integration Compatibility Notes + +## Gateway Compatibility + +The gateway handles app IDs as hex-encoded strings internally. For NEAR: +- App IDs in attestations are still bytes (as per dstack spec) +- auth-near converts hex app_id to NEAR AccountId format when calling contracts +- Gateway routing uses app_id from SNI parsing - works with any string format +- **Status**: ✅ Compatible - no changes needed + +## Guest Agent Compatibility + +The guest-agent uses app_id as `Vec` internally: +- App IDs come from attestation (bytes) +- Encoded/decoded as hex strings when needed +- auth-near handles the conversion to NEAR AccountId format +- **Status**: ✅ Compatible - no changes needed + +## App ID Format Considerations + +- **Attestation**: App IDs are always bytes (hex-encoded) +- **NEAR Contracts**: Expect AccountId (string format like "app.near") +- **Conversion**: auth-near converts hex → AccountId when calling NEAR contracts +- **Storage**: Gateway/guest-agent store app_id as hex strings internally + +## Potential Future Enhancements + +1. **App ID Mapping**: Consider a mapping layer if hex addresses need to map to NEAR AccountIds +2. **SNI Parsing**: Gateway SNI parsing works with any string format, but ensure AccountId format is URL-safe +3. **Validation**: Add validation to ensure AccountId format is valid NEAR account ID + + diff --git a/kms/auth-near/README.md b/kms/auth-near/README.md new file mode 100644 index 00000000..b02cb087 --- /dev/null +++ b/kms/auth-near/README.md @@ -0,0 +1,355 @@ +# dstack auth-near + +A NEAR Protocol backend for dstack KMS webhook authorization. Validates boot requests against NEAR smart contracts. + +## Overview + +This module provides on-chain governance authentication for dstack KMS using NEAR Protocol smart contracts. It's similar to `auth-eth` but uses NEAR's contract system instead of Ethereum. + +## Features + +- **NEAR Contract Integration**: Calls `is_kms_allowed()` and `is_app_allowed()` view methods on NEAR contracts +- **Multi-Contract Support**: Validates both KMS and App contracts +- **Account ID Handling**: Converts hex addresses to NEAR AccountId format +- **Compatible API**: Same HTTP endpoints as `auth-eth` for seamless integration + +## Prerequisites + +- Bun runtime (or Node.js with appropriate setup) +- NEAR RPC endpoint access +- Deployed NEAR KMS contract +- Deployed NEAR App contracts (for app validation) + +## Installation + +```bash +bun install +``` + +## Configuration + +Set the following environment variables: + +| Variable | Description | Default | +|----------|-------------|---------| +| `NEAR_RPC_URL` | NEAR RPC endpoint | `https://free.rpc.fastnear.com` | +| `NEAR_NETWORK_ID` | NEAR network ID (`mainnet`, `testnet`, `betanet`) | `mainnet` | +| `KMS_CONTRACT_ID` | NEAR account ID of the KMS contract | (required) | +| `PORT` | Server port | `3000` | + +## Usage + +### Start the server + +```bash +bun run start +``` + +Or in development mode with auto-reload: + +```bash +bun run dev +``` + +### Endpoints + +#### GET / + +Health check and info endpoint. Returns contract information. + +**Response:** +```json +{ + "status": "ok", + "kmsContractAddr": "kms.dstack.near", + "gatewayAppId": "gateway.dstack.near", + "chainId": "mainnet", + "appAuthImplementation": "", + "appImplementation": "" +} +``` + +#### POST /bootAuth/app + +App boot authorization. + +**Request:** JSON body matching `BootInfo` schema: +```json +{ + "mrAggregated": "hex_string", + "osImageHash": "hex_string", + "appId": "hex_string", + "composeHash": "hex_string", + "instanceId": "hex_string", + "deviceId": "hex_string", + "tcbStatus": "UpToDate", + "advisoryIds": [], + "mrSystem": "hex_string" +} +``` + +**Response:** +```json +{ + "isAllowed": true, + "reason": "", + "gatewayAppId": "gateway.dstack.near" +} +``` + +#### POST /bootAuth/kms + +KMS boot authorization. + +**Request:** Same as `/bootAuth/app` + +**Response:** Same as `/bootAuth/app` + +## Integration with dstack-kms + +Configure KMS to use webhook auth pointing to this server: + +```toml +[core.auth_api] +type = "webhook" + +[core.auth_api.webhook] +url = "http://auth-near:3000" +``` + +Or use the new NEAR-specific config: + +```toml +[core.auth_api] +type = "near" + +[core.auth_api.near] +rpc_url = "https://free.rpc.fastnear.com" +network_id = "mainnet" +contract_id = "kms.dstack.near" +``` + +## Differences from auth-eth + +| Aspect | auth-eth | auth-near | +|--------|----------|-----------| +| Blockchain | Ethereum/Base | NEAR Protocol | +| SDK | viem | near-api-js | +| Address Format | 20-byte hex (0x...) | AccountId (string) | +| Contract Calls | `readContract()` | `viewFunction()` | +| Network Config | Chain ID (number) | Network ID (string) | + +## CLI Commands + +The `auth-near` package includes CLI commands for deploying app contracts and managing compose hashes, similar to Hardhat tasks in `auth-eth`. + +### Environment Variables + +For CLI commands, you need to set: + +| Variable | Description | Required | +|----------|-------------|----------| +| `NEAR_ACCOUNT_ID` | NEAR account ID for signing transactions | Yes | +| `NEAR_PRIVATE_KEY` | NEAR account private key (ed25519:...) | Yes | +| `KMS_CONTRACT_ID` | NEAR account ID of the KMS contract | Yes (for deploy) | +| `NEAR_NETWORK_ID` | Network ID (`testnet`, `mainnet`) | No (default: `testnet`) | +| `NEAR_RPC_URL` | NEAR RPC endpoint | No (auto-detected) | + +### Deploy App Contract + +Deploy a new app contract via the KMS contract's `register_app` function: + +```bash +bun run app:deploy [options] +``` + +**Example:** +```bash +export NEAR_ACCOUNT_ID=owner.testnet +export NEAR_PRIVATE_KEY=ed25519:... +export KMS_CONTRACT_ID=kms.testnet +export NEAR_NETWORK_ID=testnet + +bun run app:deploy myapp owner.testnet \ + --allow-any-device \ + --compose-hash 0x1234... +``` + +**Options:** +- `--disable-upgrades` - Disable contract upgrades +- `--allow-any-device` - Allow any device to boot this app +- `--device-id ` - Initial device ID to allow +- `--compose-hash ` - Initial compose hash to allow (hex string) +- `--deposit ` - Deposit amount in NEAR (default: 30 NEAR) + +**Note:** The app contract will be deployed as a subaccount: `{app_id}.{kms_contract_id}` + +### Add Compose Hash + +Add a compose hash to an existing app contract: + +```bash +bun run app:add-hash +``` + +**Example:** +```bash +export NEAR_ACCOUNT_ID=owner.testnet +export NEAR_PRIVATE_KEY=ed25519:... + +bun run app:add-hash myapp.kms.testnet 0xabcd1234... +``` + +**Note:** The `app_account_id` should be the full account ID (e.g., `myapp.kms.testnet`), not just the app ID. + +### Remove Compose Hash + +Remove a compose hash from an app contract: + +```bash +bun run app:remove-hash +``` + +**Example:** +```bash +bun run app:remove-hash myapp.kms.testnet 0xabcd1234... +``` + +### Add OS Image Hash + +Add an OS image hash to the KMS contract's allowed list: + +```bash +bun cli.ts add-os-image +``` + +**Example:** +```bash +export NEAR_ACCOUNT_ID=owner.testnet +export NEAR_PRIVATE_KEY=ed25519:... +export KMS_CONTRACT_ID=kms.testnet + +bun cli.ts add-os-image 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef +``` + +**Note:** The hash can be provided with or without the `0x` prefix. The CLI will automatically strip it if present. + +### Remove OS Image Hash + +Remove an OS image hash from the KMS contract: + +```bash +bun cli.ts remove-os-image +``` + +**Example:** +```bash +bun cli.ts remove-os-image 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef +``` + +### Add KMS Device ID + +Add a device ID to the KMS contract's allowed device list: + +```bash +bun cli.ts add-device +``` + +**Example:** +```bash +export NEAR_ACCOUNT_ID=owner.testnet +export NEAR_PRIVATE_KEY=ed25519:... +export KMS_CONTRACT_ID=kms.testnet + +bun cli.ts add-device 0xdevice1234567890abcdef1234567890abcdef1234567890abcdef1234567890 +``` + +**Note:** Device IDs are typically hex strings. The `0x` prefix is optional. + +### Remove KMS Device ID + +Remove a device ID from the KMS contract: + +```bash +bun cli.ts remove-device +``` + +**Example:** +```bash +bun cli.ts remove-device 0xdevice1234567890abcdef1234567890abcdef1234567890abcdef1234567890 +``` + +### Add KMS Aggregated MR + +Add an aggregated MR (measurement) to the KMS contract's allowed list: + +```bash +bun cli.ts add-mr +``` + +**Example:** +```bash +export NEAR_ACCOUNT_ID=owner.testnet +export NEAR_PRIVATE_KEY=ed25519:... +export KMS_CONTRACT_ID=kms.testnet + +bun cli.ts add-mr 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 +``` + +**Note:** Aggregated MRs are hex strings representing TEE measurements. The `0x` prefix is optional. + +### Remove KMS Aggregated MR + +Remove an aggregated MR from the KMS contract: + +```bash +bun cli.ts remove-mr +``` + +**Example:** +```bash +bun cli.ts remove-mr 0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 +``` + +### Direct CLI Usage + +You can also use the CLI directly with all available commands: + +```bash +# App management +bun cli.ts deploy [options] +bun cli.ts add-hash +bun cli.ts remove-hash + +# KMS configuration +bun cli.ts add-os-image +bun cli.ts remove-os-image +bun cli.ts add-device +bun cli.ts remove-device +bun cli.ts add-mr +bun cli.ts remove-mr +``` + +**Note:** All KMS configuration commands require the `KMS_CONTRACT_ID` environment variable to be set. + +## Development + +### Running Tests + +```bash +bun run test +``` + +### Linting + +```bash +bun run lint +``` + +## See Also + +- [auth-eth](../auth-eth/) - Ethereum-based auth server +- [auth-simple](../auth-simple/) - Config-based auth server +- [auth-mock](../auth-mock/) - Development/testing auth server (always allows) + + diff --git a/kms/auth-near/bun.lock b/kms/auth-near/bun.lock new file mode 100644 index 00000000..63870580 --- /dev/null +++ b/kms/auth-near/bun.lock @@ -0,0 +1,446 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "auth-near", + "dependencies": { + "@hono/zod-validator": "0.2.2", + "hono": "4.11.4", + "near-api-js": "^2.1.4", + "zod": "3.25.76", + }, + "devDependencies": { + "@types/bun": "1.2.18", + "@types/node": "^24.0.14", + "@vitest/ui": "1.6.1", + "openapi-types": "12.1.3", + "oxlint": "0.9.10", + "typescript": "5.8.3", + "vitest": "1.6.1", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + + "@hono/zod-validator": ["@hono/zod-validator@0.2.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-dSDxaPV70Py8wuIU2QNpoVEIOSzSXZ/6/B/h4xA7eOMz7+AarKTSGV8E6QwrdcCbBLkpqfJ4Q2TmBO0eP1tCBQ=="], + + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@near-js/accounts": ["@near-js/accounts@0.1.4", "", { "dependencies": { "@near-js/crypto": "0.0.5", "@near-js/providers": "0.0.7", "@near-js/signers": "0.0.5", "@near-js/transactions": "0.2.1", "@near-js/types": "0.0.4", "@near-js/utils": "0.0.4", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "bn.js": "5.2.1", "borsh": "^0.7.0", "depd": "^2.0.0", "near-abi": "0.1.1" } }, "sha512-zHFmL4OUZ4qHXOE+dDBkYgTNHLWC5RmYUVp9LiuGciO5zFPp7WlxmowJL0QjgXqV1w+dNXq3mgmkfAgYVS8Xjw=="], + + "@near-js/crypto": ["@near-js/crypto@0.0.5", "", { "dependencies": { "@near-js/types": "0.0.4", "bn.js": "5.2.1", "borsh": "^0.7.0", "tweetnacl": "^1.0.1" } }, "sha512-nbQ971iYES5Spiolt+p568gNuZ//HeMHm3qqT3xT+i8ZzgbC//l6oRf48SUVTPAboQ1TJ5dW/NqcxOY0pe7b4g=="], + + "@near-js/keystores": ["@near-js/keystores@0.0.5", "", { "dependencies": { "@near-js/crypto": "0.0.5", "@near-js/types": "0.0.4" } }, "sha512-kxqV+gw/3L8/axe9prhlU+M0hfybkxX54xfI0EEpWP2QiUV+qw+jkKolYIbdk5tdEZrGf9jHawh1yFtwP7APPQ=="], + + "@near-js/keystores-browser": ["@near-js/keystores-browser@0.0.5", "", { "dependencies": { "@near-js/crypto": "0.0.5", "@near-js/keystores": "0.0.5" } }, "sha512-mHF3Vcvsr7xnkaM/reOyxtykbE3OWKV6vQzqyTH2tZYT2OTEnj0KhRT9BCFC0Ra67K1zQLbg49Yc/kDCc5qupA=="], + + "@near-js/keystores-node": ["@near-js/keystores-node@0.0.5", "", { "dependencies": { "@near-js/crypto": "0.0.5", "@near-js/keystores": "0.0.5" } }, "sha512-BYmWyGNydfAqi7eYA1Jo8zULL13cxejD2VBr0BBIXx5bJ+BO4TLecsY1xdTBEq06jyWXHa7kV4h8BJzAjvpTLg=="], + + "@near-js/providers": ["@near-js/providers@0.0.7", "", { "dependencies": { "@near-js/transactions": "0.2.1", "@near-js/types": "0.0.4", "@near-js/utils": "0.0.4", "bn.js": "5.2.1", "borsh": "^0.7.0", "http-errors": "^1.7.2" }, "optionalDependencies": { "node-fetch": "^2.6.1" } }, "sha512-qj16Ey+vSw7lHE85xW+ykYJoLPr4A6Q/TsfpwhJLS6zBInSC6sKVqPO1L8bK4VA/yB7V7JJPor9UVCWgRXdNEA=="], + + "@near-js/signers": ["@near-js/signers@0.0.5", "", { "dependencies": { "@near-js/crypto": "0.0.5", "@near-js/keystores": "0.0.5", "js-sha256": "^0.9.0" } }, "sha512-XJjYYatehxHakHa7WAoiQ8uIBSWBR2EnO4GzlIe8qpWL+LoH4t68MSezC1HwT546y9YHIvePjwDrBeYk8mg20w=="], + + "@near-js/transactions": ["@near-js/transactions@0.2.1", "", { "dependencies": { "@near-js/crypto": "0.0.5", "@near-js/signers": "0.0.5", "@near-js/types": "0.0.4", "@near-js/utils": "0.0.4", "bn.js": "5.2.1", "borsh": "^0.7.0", "js-sha256": "^0.9.0" } }, "sha512-V9tXzkICDPruSxihKXkBhUgsI4uvW7TwXlnZS2GZpPsFFiIUeGrso0wo4uiQwB6miFA5q6fKaAtQa4F2v1s+zg=="], + + "@near-js/types": ["@near-js/types@0.0.4", "", { "dependencies": { "bn.js": "5.2.1" } }, "sha512-8TTMbLMnmyG06R5YKWuS/qFG1tOA3/9lX4NgBqQPsvaWmDsa+D+QwOkrEHDegped0ZHQwcjAXjKML1S1TyGYKg=="], + + "@near-js/utils": ["@near-js/utils@0.0.4", "", { "dependencies": { "@near-js/types": "0.0.4", "bn.js": "5.2.1", "depd": "^2.0.0", "mustache": "^4.0.0" } }, "sha512-mPUEPJbTCMicGitjEGvQqOe8AS7O4KkRCxqd0xuE/X6gXF1jz1pYMZn4lNUeUz2C84YnVSGLAM0o9zcN6Y4hiA=="], + + "@near-js/wallet-account": ["@near-js/wallet-account@0.0.7", "", { "dependencies": { "@near-js/accounts": "0.1.4", "@near-js/crypto": "0.0.5", "@near-js/keystores": "0.0.5", "@near-js/signers": "0.0.5", "@near-js/transactions": "0.2.1", "@near-js/types": "0.0.4", "@near-js/utils": "0.0.4", "bn.js": "5.2.1", "borsh": "^0.7.0" } }, "sha512-tmRyieG/wHmuNkg/WGFyKD6iH6atHPbY0rZ5OjOIiteuhZEPgp+z8OBpiQ4qumTa63q46aj/QVSQL0J3+JmBfw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@0.9.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eOXKZYq5bnCSgDefgM5bzAg+4Fc//Rc4yjgKN8iDWUARweCaChiQXb6TXX8MfEfs6qayEMy6yVj0pqoFz0B1aw=="], + + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@0.9.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-UeYICDvLUaUOcY+0ugZUEmBMRLP+x8iTgL7TeY6BlpGw2ahbtUOTbyIIRWtr/0O++TnjZ+v8TzhJ9crw6Ij6dg=="], + + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@0.9.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-0Zn+vqHhrZyufFBfq9WOgiIool0gCR14BLsdS+0Dwd9o+kNxPGA5q7erQFkiC4rpkxtfBHeD3iIKMMt7d29Kyw=="], + + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@0.9.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-tkQcWpYwF42bA/uRaV2iMFePHkBjTTgomOgeEaiw6XOSJX4nBEqGIIboqqLBWT4JnKCf/L+IG3y/e1MflhKByw=="], + + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@0.9.10", "", { "os": "linux", "cpu": "x64" }, "sha512-JHbkMUnibqaSMBvLHyqTL5cWxcGW+jw+Ppt2baLISpvo34a6fBR+PI7v/A92sEDWe0W1rPhypzCwA8mKpkQ3DA=="], + + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@0.9.10", "", { "os": "linux", "cpu": "x64" }, "sha512-aBBwN7bQzidwHwEXr7BAdVvMTLWstCy5gikerjLnGDeCSXX9r+o6+yUzTOqZvOo66E+XBgOJaVbY8rsL1MLE0g=="], + + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@0.9.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-LXDnk7vKHT3IY6G1jq0O7+XMhtcHOYuxLGIx4KP+4xS6vKgBY+Bsq4xV3AtmtKlvnXkP5FxHpfLmcEtm5AWysA=="], + + "@oxlint/win32-x64": ["@oxlint/win32-x64@0.9.10", "", { "os": "win32", "cpu": "x64" }, "sha512-w5XRAV4bhgwenjjpGYZGglqzG9Wv/sI+cjQWJBQsvfDXsr2w4vOBXzt1j3/Z3EcSqf4KtkCa/IIuAhQyeShUbA=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.56.0", "", { "os": "android", "cpu": "arm" }, "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.56.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.56.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.56.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.56.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.56.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.56.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.56.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.56.0", "", { "os": "none", "cpu": "arm64" }, "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.56.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.56.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + + "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@24.10.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw=="], + + "@types/react": ["@types/react@19.2.9", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA=="], + + "@vitest/expect": ["@vitest/expect@1.6.1", "", { "dependencies": { "@vitest/spy": "1.6.1", "@vitest/utils": "1.6.1", "chai": "^4.3.10" } }, "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog=="], + + "@vitest/runner": ["@vitest/runner@1.6.1", "", { "dependencies": { "@vitest/utils": "1.6.1", "p-limit": "^5.0.0", "pathe": "^1.1.1" } }, "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA=="], + + "@vitest/snapshot": ["@vitest/snapshot@1.6.1", "", { "dependencies": { "magic-string": "^0.30.5", "pathe": "^1.1.1", "pretty-format": "^29.7.0" } }, "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ=="], + + "@vitest/spy": ["@vitest/spy@1.6.1", "", { "dependencies": { "tinyspy": "^2.2.0" } }, "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw=="], + + "@vitest/ui": ["@vitest/ui@1.6.1", "", { "dependencies": { "@vitest/utils": "1.6.1", "fast-glob": "^3.3.2", "fflate": "^0.8.1", "flatted": "^3.2.9", "pathe": "^1.1.1", "picocolors": "^1.0.0", "sirv": "^2.0.4" }, "peerDependencies": { "vitest": "1.6.1" } }, "sha512-xa57bCPGuzEFqGjPs3vVLyqareG8DX0uMkr5U/v5vLv5/ZUrBrPL7gzxzTJedEyZxFMfsozwTIbbYfEQVo3kgg=="], + + "@vitest/utils": ["@vitest/utils@1.6.1", "", { "dependencies": { "diff-sequences": "^29.6.3", "estree-walker": "^3.0.3", "loupe": "^2.3.7", "pretty-format": "^29.7.0" } }, "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], + + "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "assertion-error": ["assertion-error@1.1.0", "", {}, "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="], + + "base-x": ["base-x@3.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA=="], + + "bn.js": ["bn.js@5.2.1", "", {}, "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="], + + "borsh": ["borsh@0.7.0", "", { "dependencies": { "bn.js": "^5.2.0", "bs58": "^4.0.0", "text-encoding-utf-8": "^1.0.2" } }, "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bs58": ["bs58@4.0.1", "", { "dependencies": { "base-x": "^3.0.2" } }, "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw=="], + + "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "capability": ["capability@0.2.5", "", {}, "sha512-rsJZYVCgXd08sPqwmaIqjAd5SUTfonV0z/gDJ8D6cN8wQphky1kkAYEqQ+hmDxTw7UihvBfjUVUSY+DBEe44jg=="], + + "chai": ["chai@4.5.0", "", { "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", "deep-eql": "^4.1.3", "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", "type-detect": "^4.1.0" } }, "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw=="], + + "check-error": ["check-error@1.0.3", "", { "dependencies": { "get-func-name": "^2.0.2" } }, "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-eql": ["deep-eql@4.1.4", "", { "dependencies": { "type-detect": "^4.0.0" } }, "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg=="], + + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + + "error-polyfill": ["error-polyfill@0.1.3", "", { "dependencies": { "capability": "^0.2.5", "o3": "^1.0.3", "u3": "^0.1.1" } }, "sha512-XHJk60ufE+TG/ydwp4lilOog549iiQF2OAPhkk9DdiYWMrltz5yhDz/xnKuenNwP7gy3dsibssO5QpVhkrSzzg=="], + + "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-func-name": ["get-func-name@2.0.2", "", {}, "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ=="], + + "get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="], + + "http-errors": ["http-errors@1.8.1", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.1" } }, "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g=="], + + "human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-sha256": ["js-sha256@0.9.0", "", {}, "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="], + + "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "local-pkg": ["local-pkg@0.5.1", "", { "dependencies": { "mlly": "^1.7.3", "pkg-types": "^1.2.1" } }, "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ=="], + + "loupe": ["loupe@2.3.7", "", { "dependencies": { "get-func-name": "^2.0.1" } }, "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mimic-fn": ["mimic-fn@4.0.0", "", {}, "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw=="], + + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "near-abi": ["near-abi@0.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-RVDI8O+KVxRpC3KycJ1bpfVj9Zv+xvq9PlW1yIFl46GhrnLw83/72HqHGjGDjQ8DtltkcpSjY9X3YIGZ+1QyzQ=="], + + "near-api-js": ["near-api-js@2.1.4", "", { "dependencies": { "@near-js/accounts": "0.1.4", "@near-js/crypto": "0.0.5", "@near-js/keystores": "0.0.5", "@near-js/keystores-browser": "0.0.5", "@near-js/keystores-node": "0.0.5", "@near-js/providers": "0.0.7", "@near-js/signers": "0.0.5", "@near-js/transactions": "0.2.1", "@near-js/types": "0.0.4", "@near-js/utils": "0.0.4", "@near-js/wallet-account": "0.0.7", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "bn.js": "5.2.1", "borsh": "^0.7.0", "depd": "^2.0.0", "error-polyfill": "^0.1.3", "http-errors": "^1.7.2", "near-abi": "0.1.1", "node-fetch": "^2.6.1", "tweetnacl": "^1.0.1" } }, "sha512-e1XicyvJvQMtu7qrG8oWyAdjHJJCoy+cvbW6h2Dky4yj7vC85omQz/x7IgKl71VhzDj2/TGUwjTVESp6NSe75A=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + + "o3": ["o3@1.0.3", "", { "dependencies": { "capability": "^0.2.5" } }, "sha512-f+4n+vC6s4ysy7YO7O2gslWZBUu8Qj2i2OUJOvjRxQva7jVjYjB29jrr9NCjmxZQR0gzrOcv1RnqoYOeMs5VRQ=="], + + "onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="], + + "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + + "oxlint": ["oxlint@0.9.10", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "0.9.10", "@oxlint/darwin-x64": "0.9.10", "@oxlint/linux-arm64-gnu": "0.9.10", "@oxlint/linux-arm64-musl": "0.9.10", "@oxlint/linux-x64-gnu": "0.9.10", "@oxlint/linux-x64-musl": "0.9.10", "@oxlint/win32-arm64": "0.9.10", "@oxlint/win32-x64": "0.9.10" }, "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-bKiiFN7Hnoaist/rditTRBXz+GXKYuLd53/NB7Q6zHB/bifELJarSoRLkAUGElIJKl4PSr3lTh1g6zehh+rX0g=="], + + "p-limit": ["p-limit@5.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "pathval": ["pathval@1.1.1", "", {}, "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.56.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.56.0", "@rollup/rollup-android-arm64": "4.56.0", "@rollup/rollup-darwin-arm64": "4.56.0", "@rollup/rollup-darwin-x64": "4.56.0", "@rollup/rollup-freebsd-arm64": "4.56.0", "@rollup/rollup-freebsd-x64": "4.56.0", "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", "@rollup/rollup-linux-arm-musleabihf": "4.56.0", "@rollup/rollup-linux-arm64-gnu": "4.56.0", "@rollup/rollup-linux-arm64-musl": "4.56.0", "@rollup/rollup-linux-loong64-gnu": "4.56.0", "@rollup/rollup-linux-loong64-musl": "4.56.0", "@rollup/rollup-linux-ppc64-gnu": "4.56.0", "@rollup/rollup-linux-ppc64-musl": "4.56.0", "@rollup/rollup-linux-riscv64-gnu": "4.56.0", "@rollup/rollup-linux-riscv64-musl": "4.56.0", "@rollup/rollup-linux-s390x-gnu": "4.56.0", "@rollup/rollup-linux-x64-gnu": "4.56.0", "@rollup/rollup-linux-x64-musl": "4.56.0", "@rollup/rollup-openbsd-x64": "4.56.0", "@rollup/rollup-openharmony-arm64": "4.56.0", "@rollup/rollup-win32-arm64-msvc": "4.56.0", "@rollup/rollup-win32-ia32-msvc": "4.56.0", "@rollup/rollup-win32-x64-gnu": "4.56.0", "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sirv": ["sirv@2.0.4", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="], + + "strip-literal": ["strip-literal@2.1.1", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q=="], + + "text-encoding-utf-8": ["text-encoding-utf-8@1.0.2", "", {}, "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinypool": ["tinypool@0.8.4", "", {}, "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ=="], + + "tinyspy": ["tinyspy@2.2.1", "", {}, "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="], + + "type-detect": ["type-detect@4.1.0", "", {}, "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "u3": ["u3@0.1.1", "", {}, "sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w=="], + + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vite-node": ["vite-node@1.6.1", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.4", "pathe": "^1.1.1", "picocolors": "^1.0.0", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA=="], + + "vitest": ["vitest@1.6.1", "", { "dependencies": { "@vitest/expect": "1.6.1", "@vitest/runner": "1.6.1", "@vitest/snapshot": "1.6.1", "@vitest/spy": "1.6.1", "@vitest/utils": "1.6.1", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", "execa": "^8.0.1", "local-pkg": "^0.5.0", "magic-string": "^0.30.5", "pathe": "^1.1.1", "picocolors": "^1.0.0", "std-env": "^3.5.0", "strip-literal": "^2.0.0", "tinybench": "^2.5.1", "tinypool": "^0.8.3", "vite": "^5.0.0", "vite-node": "1.6.1", "why-is-node-running": "^2.2.2" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "1.6.1", "@vitest/ui": "1.6.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="], + + "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + } +} diff --git a/kms/auth-near/cli.ts b/kms/auth-near/cli.ts new file mode 100644 index 00000000..f72ea7dd --- /dev/null +++ b/kms/auth-near/cli.ts @@ -0,0 +1,603 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +import { connect, keyStores, Near, Account, KeyPair, utils } from 'near-api-js'; +import { parseNearAmount } from 'near-api-js/lib/utils/format'; + +// Helper function to load account from private key +async function getAccount( + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const keyStore = new keyStores.InMemoryKeyStore(); + const keyPair = KeyPair.fromString(privateKey); + keyStore.setKey(networkId, accountId, keyPair); + + const near = await connect({ + networkId, + nodeUrl: rpcUrl, + keyStore, + }); + + return await near.account(accountId); +} + +// Helper function to get app account ID (subaccount of KMS) +function getAppAccountId(appId: string, kmsContractId: string): string { + return `${appId}.${kmsContractId}`; +} + +// Deploy app contract via KMS register_app +async function deployApp( + kmsContractId: string, + appId: string, + ownerId: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string, + options: { + disableUpgrades?: boolean; + allowAnyDevice?: boolean; + initialDeviceId?: string; + initialComposeHash?: string; + deposit?: string; // in NEAR + } = {} +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + const appAccountId = getAppAccountId(appId, kmsContractId); + + console.log(`Deploying app contract...`); + console.log(` KMS Contract: ${kmsContractId}`); + console.log(` App ID: ${appId}`); + console.log(` App Account: ${appAccountId}`); + console.log(` Owner: ${ownerId}`); + + const args: any = { + app_id: appId, + owner_id: ownerId, + disable_upgrades: options.disableUpgrades ?? false, + allow_any_device: options.allowAnyDevice ?? false, + }; + + if (options.initialDeviceId) { + args.initial_device_id = options.initialDeviceId; + } else { + args.initial_device_id = null; + } + + if (options.initialComposeHash) { + args.initial_compose_hash = options.initialComposeHash; + } else { + args.initial_compose_hash = null; + } + + const deposit = options.deposit + ? parseNearAmount(options.deposit) + : parseNearAmount('30'); // Default 30 NEAR for account creation + deployment + + if (!deposit) { + throw new Error('Failed to parse deposit amount'); + } + + try { + const result = await account.functionCall({ + contractId: kmsContractId, + methodName: 'register_app', + args, + gas: BigInt('300000000000000'), // 300 TGas + attachedDeposit: BigInt(deposit), + }); + + console.log(`✅ App contract deployed successfully!`); + console.log(` App Account: ${appAccountId}`); + console.log(` Transaction: ${result.transaction.hash}`); + return appAccountId; + } catch (error) { + console.error('❌ Failed to deploy app contract:', error); + throw error; + } +} + +// Add compose hash to app contract +async function addComposeHash( + appAccountId: string, + composeHash: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex hash to string (remove 0x prefix if present) + const hashString = composeHash.startsWith('0x') ? composeHash.slice(2) : composeHash; + + console.log(`Adding compose hash to app contract...`); + console.log(` App Account: ${appAccountId}`); + console.log(` Compose Hash: ${hashString}`); + + try { + const result = await account.functionCall({ + contractId: appAccountId, + methodName: 'add_compose_hash', + args: { compose_hash: hashString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ Compose hash added successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to add compose hash:', error); + throw error; + } +} + +// Remove compose hash from app contract +async function removeComposeHash( + appAccountId: string, + composeHash: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex hash to string (remove 0x prefix if present) + const hashString = composeHash.startsWith('0x') ? composeHash.slice(2) : composeHash; + + console.log(`Removing compose hash from app contract...`); + console.log(` App Account: ${appAccountId}`); + console.log(` Compose Hash: ${hashString}`); + + try { + const result = await account.functionCall({ + contractId: appAccountId, + methodName: 'remove_compose_hash', + args: { compose_hash: hashString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ Compose hash removed successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to remove compose hash:', error); + throw error; + } +} + +// Add OS image hash to KMS contract +async function addOsImageHash( + kmsContractId: string, + osImageHash: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex hash to string (remove 0x prefix if present) + const hashString = osImageHash.startsWith('0x') ? osImageHash.slice(2) : osImageHash; + + console.log(`Adding OS image hash to KMS contract...`); + console.log(` KMS Contract: ${kmsContractId}`); + console.log(` OS Image Hash: ${hashString}`); + + try { + const result = await account.functionCall({ + contractId: kmsContractId, + methodName: 'add_os_image_hash', + args: { os_image_hash: hashString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ OS image hash added successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to add OS image hash:', error); + throw error; + } +} + +// Remove OS image hash from KMS contract +async function removeOsImageHash( + kmsContractId: string, + osImageHash: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex hash to string (remove 0x prefix if present) + const hashString = osImageHash.startsWith('0x') ? osImageHash.slice(2) : osImageHash; + + console.log(`Removing OS image hash from KMS contract...`); + console.log(` KMS Contract: ${kmsContractId}`); + console.log(` OS Image Hash: ${hashString}`); + + try { + const result = await account.functionCall({ + contractId: kmsContractId, + methodName: 'remove_os_image_hash', + args: { os_image_hash: hashString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ OS image hash removed successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to remove OS image hash:', error); + throw error; + } +} + +// Add KMS device ID to KMS contract +async function addKmsDevice( + kmsContractId: string, + deviceId: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex device ID to string (remove 0x prefix if present) + const deviceIdString = deviceId.startsWith('0x') ? deviceId.slice(2) : deviceId; + + console.log(`Adding KMS device ID to KMS contract...`); + console.log(` KMS Contract: ${kmsContractId}`); + console.log(` Device ID: ${deviceIdString}`); + + try { + const result = await account.functionCall({ + contractId: kmsContractId, + methodName: 'add_kms_device', + args: { device_id: deviceIdString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ KMS device ID added successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to add KMS device ID:', error); + throw error; + } +} + +// Remove KMS device ID from KMS contract +async function removeKmsDevice( + kmsContractId: string, + deviceId: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex device ID to string (remove 0x prefix if present) + const deviceIdString = deviceId.startsWith('0x') ? deviceId.slice(2) : deviceId; + + console.log(`Removing KMS device ID from KMS contract...`); + console.log(` KMS Contract: ${kmsContractId}`); + console.log(` Device ID: ${deviceIdString}`); + + try { + const result = await account.functionCall({ + contractId: kmsContractId, + methodName: 'remove_kms_device', + args: { device_id: deviceIdString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ KMS device ID removed successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to remove KMS device ID:', error); + throw error; + } +} + +// Add KMS aggregated MR to KMS contract +async function addKmsAggregatedMr( + kmsContractId: string, + mrAggregated: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex MR to string (remove 0x prefix if present) + const mrString = mrAggregated.startsWith('0x') ? mrAggregated.slice(2) : mrAggregated; + + console.log(`Adding KMS aggregated MR to KMS contract...`); + console.log(` KMS Contract: ${kmsContractId}`); + console.log(` Aggregated MR: ${mrString}`); + + try { + const result = await account.functionCall({ + contractId: kmsContractId, + methodName: 'add_kms_aggregated_mr', + args: { mr_aggregated: mrString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ KMS aggregated MR added successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to add KMS aggregated MR:', error); + throw error; + } +} + +// Remove KMS aggregated MR from KMS contract +async function removeKmsAggregatedMr( + kmsContractId: string, + mrAggregated: string, + accountId: string, + privateKey: string, + networkId: string, + rpcUrl: string +): Promise { + const account = await getAccount(accountId, privateKey, networkId, rpcUrl); + + // Convert hex MR to string (remove 0x prefix if present) + const mrString = mrAggregated.startsWith('0x') ? mrAggregated.slice(2) : mrAggregated; + + console.log(`Removing KMS aggregated MR from KMS contract...`); + console.log(` KMS Contract: ${kmsContractId}`); + console.log(` Aggregated MR: ${mrString}`); + + try { + const result = await account.functionCall({ + contractId: kmsContractId, + methodName: 'remove_kms_aggregated_mr', + args: { mr_aggregated: mrString }, + gas: BigInt('100000000000000'), // 100 TGas + attachedDeposit: BigInt('1'), // 1 yoctoNEAR + }); + + console.log(`✅ KMS aggregated MR removed successfully!`); + console.log(` Transaction: ${result.transaction.hash}`); + } catch (error) { + console.error('❌ Failed to remove KMS aggregated MR:', error); + throw error; + } +} + +// CLI interface +async function main() { + const args = process.argv.slice(2); + const command = args[0]; + + // Get environment variables + const networkId = process.env.NEAR_NETWORK_ID || process.env.NEAR_ENV || 'testnet'; + const rpcUrl = + process.env.NEAR_RPC_URL || + (networkId === 'mainnet' ? 'https://rpc.near.org' : 'https://rpc.testnet.near.org'); + const kmsContractId = process.env.KMS_CONTRACT_ID || ''; + const accountId = process.env.NEAR_ACCOUNT_ID || ''; + const privateKey = process.env.NEAR_PRIVATE_KEY || ''; + + if (!accountId || !privateKey) { + console.error('❌ NEAR_ACCOUNT_ID and NEAR_PRIVATE_KEY environment variables are required'); + process.exit(1); + } + + if (command === 'deploy') { + // Usage: bun cli.ts deploy [options] + if (args.length < 3) { + console.error('Usage: bun cli.ts deploy [--disable-upgrades] [--allow-any-device] [--device-id ] [--compose-hash ] [--deposit ]'); + process.exit(1); + } + + if (!kmsContractId) { + console.error('❌ KMS_CONTRACT_ID environment variable is required for deployment'); + process.exit(1); + } + + const appId = args[1]; + const ownerId = args[2]; + const options: any = {}; + + // Parse options + for (let i = 3; i < args.length; i++) { + if (args[i] === '--disable-upgrades') { + options.disableUpgrades = true; + } else if (args[i] === '--allow-any-device') { + options.allowAnyDevice = true; + } else if (args[i] === '--device-id' && i + 1 < args.length) { + options.initialDeviceId = args[++i]; + } else if (args[i] === '--compose-hash' && i + 1 < args.length) { + options.initialComposeHash = args[++i]; + } else if (args[i] === '--deposit' && i + 1 < args.length) { + options.deposit = args[++i]; + } + } + + await deployApp( + kmsContractId, + appId, + ownerId, + accountId, + privateKey, + networkId, + rpcUrl, + options + ); + } else if (command === 'add-hash') { + // Usage: bun cli.ts add-hash + if (args.length < 3) { + console.error('Usage: bun cli.ts add-hash '); + console.error(' app_account_id: Full account ID (e.g., app-id.kms-contract.near)'); + console.error(' compose_hash: Hex string (with or without 0x prefix)'); + process.exit(1); + } + + const appAccountId = args[1]; + const composeHash = args[2]; + + await addComposeHash(appAccountId, composeHash, accountId, privateKey, networkId, rpcUrl); + } else if (command === 'remove-hash') { + // Usage: bun cli.ts remove-hash + if (args.length < 3) { + console.error('Usage: bun cli.ts remove-hash '); + process.exit(1); + } + + const appAccountId = args[1]; + const composeHash = args[2]; + + await removeComposeHash(appAccountId, composeHash, accountId, privateKey, networkId, rpcUrl); + } else if (command === 'add-os-image') { + // Usage: bun cli.ts add-os-image + if (args.length < 2) { + console.error('Usage: bun cli.ts add-os-image '); + console.error(' os_image_hash: Hex string (with or without 0x prefix)'); + process.exit(1); + } + + if (!kmsContractId) { + console.error('❌ KMS_CONTRACT_ID environment variable is required'); + process.exit(1); + } + + const osImageHash = args[1]; + await addOsImageHash(kmsContractId, osImageHash, accountId, privateKey, networkId, rpcUrl); + } else if (command === 'remove-os-image') { + // Usage: bun cli.ts remove-os-image + if (args.length < 2) { + console.error('Usage: bun cli.ts remove-os-image '); + console.error(' os_image_hash: Hex string (with or without 0x prefix)'); + process.exit(1); + } + + if (!kmsContractId) { + console.error('❌ KMS_CONTRACT_ID environment variable is required'); + process.exit(1); + } + + const osImageHash = args[1]; + await removeOsImageHash(kmsContractId, osImageHash, accountId, privateKey, networkId, rpcUrl); + } else if (command === 'add-device') { + // Usage: bun cli.ts add-device + if (args.length < 2) { + console.error('Usage: bun cli.ts add-device '); + console.error(' device_id: Hex string (with or without 0x prefix)'); + process.exit(1); + } + + if (!kmsContractId) { + console.error('❌ KMS_CONTRACT_ID environment variable is required'); + process.exit(1); + } + + const deviceId = args[1]; + await addKmsDevice(kmsContractId, deviceId, accountId, privateKey, networkId, rpcUrl); + } else if (command === 'remove-device') { + // Usage: bun cli.ts remove-device + if (args.length < 2) { + console.error('Usage: bun cli.ts remove-device '); + console.error(' device_id: Hex string (with or without 0x prefix)'); + process.exit(1); + } + + if (!kmsContractId) { + console.error('❌ KMS_CONTRACT_ID environment variable is required'); + process.exit(1); + } + + const deviceId = args[1]; + await removeKmsDevice(kmsContractId, deviceId, accountId, privateKey, networkId, rpcUrl); + } else if (command === 'add-mr') { + // Usage: bun cli.ts add-mr + if (args.length < 2) { + console.error('Usage: bun cli.ts add-mr '); + console.error(' mr_aggregated: Hex string (with or without 0x prefix)'); + process.exit(1); + } + + if (!kmsContractId) { + console.error('❌ KMS_CONTRACT_ID environment variable is required'); + process.exit(1); + } + + const mrAggregated = args[1]; + await addKmsAggregatedMr(kmsContractId, mrAggregated, accountId, privateKey, networkId, rpcUrl); + } else if (command === 'remove-mr') { + // Usage: bun cli.ts remove-mr + if (args.length < 2) { + console.error('Usage: bun cli.ts remove-mr '); + console.error(' mr_aggregated: Hex string (with or without 0x prefix)'); + process.exit(1); + } + + if (!kmsContractId) { + console.error('❌ KMS_CONTRACT_ID environment variable is required'); + process.exit(1); + } + + const mrAggregated = args[1]; + await removeKmsAggregatedMr(kmsContractId, mrAggregated, accountId, privateKey, networkId, rpcUrl); + } else { + console.error('Unknown command:', command); + console.error(''); + console.error('Available commands:'); + console.error(' deploy [options] - Deploy app contract via KMS'); + console.error(' add-hash - Add compose hash to app contract'); + console.error(' remove-hash - Remove compose hash from app contract'); + console.error(' add-os-image - Add OS image hash to KMS contract'); + console.error(' remove-os-image - Remove OS image hash from KMS contract'); + console.error(' add-device - Add KMS device ID to KMS contract'); + console.error(' remove-device - Remove KMS device ID from KMS contract'); + console.error(' add-mr - Add KMS aggregated MR to KMS contract'); + console.error(' remove-mr - Remove KMS aggregated MR from KMS contract'); + console.error(''); + console.error('Environment variables:'); + console.error(' NEAR_ACCOUNT_ID - NEAR account ID (required)'); + console.error(' NEAR_PRIVATE_KEY - NEAR account private key (required)'); + console.error(' KMS_CONTRACT_ID - KMS contract ID (required for KMS operations)'); + console.error(' NEAR_NETWORK_ID - Network ID (testnet/mainnet, default: testnet)'); + console.error(' NEAR_RPC_URL - NEAR RPC URL (optional, auto-detected by network)'); + process.exit(1); + } +} + +// Run CLI if executed directly (Bun supports import.meta.main) +if (import.meta.main) { + main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); + }); +} + +export { + deployApp, + addComposeHash, + removeComposeHash, + getAppAccountId, + addOsImageHash, + removeOsImageHash, + addKmsDevice, + removeKmsDevice, + addKmsAggregatedMr, + removeKmsAggregatedMr, +}; + diff --git a/kms/auth-near/index.test.ts b/kms/auth-near/index.test.ts new file mode 100644 index 00000000..856a5d94 --- /dev/null +++ b/kms/auth-near/index.test.ts @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest'; + +// Mock near-api-js +const mockViewFunction = vi.fn(); +const mockAccount = { + viewFunction: mockViewFunction, +}; + +const mockConnect = vi.fn(() => ({ + account: vi.fn(() => Promise.resolve(mockAccount)), + config: { networkId: 'testnet' }, +})); + +vi.mock('near-api-js', () => ({ + connect: mockConnect, + keyStores: { + InMemoryKeyStore: vi.fn(), + }, +})); + +// Dynamic import after mocking +let appFetch: any; + +beforeAll(async () => { + // Set environment variables for testing + process.env.NEAR_RPC_URL = 'https://rpc.testnet.fastnear.com'; + process.env.NEAR_NETWORK_ID = 'testnet'; + process.env.KMS_CONTRACT_ID = 'kms.testnet'; + process.env.PORT = '3002'; + + // Import the app after mocking + const indexModule = await import('./index.ts'); + appFetch = indexModule.default.fetch; + + // Wait for NEAR initialization + await new Promise(resolve => setTimeout(resolve, 100)); +}); + +beforeEach(() => { + // Reset mocks before each test + vi.clearAllMocks(); +}); + +describe('auth-near API Tests', () => { + describe('GET /', () => { + it('should return system info', async () => { + // Mock contract calls + mockViewFunction.mockImplementation((params) => { + if (params.methodName === 'get_gateway_app_id') { + return Promise.resolve('gateway.testnet'); + } + return Promise.resolve(''); + }); + + const response = await appFetch(new Request('http://localhost:3002/')); + expect(response.status).toBe(200); + + const data = await response.json(); + expect(data.status).toBe('ok'); + expect(data.kmsContractAddr).toBe('kms.testnet'); + }); + }); + + describe('POST /bootAuth/app', () => { + it('should validate app boot request', async () => { + const bootInfo = { + mrAggregated: '0x' + 'a'.repeat(64), + osImageHash: '0x' + 'b'.repeat(64), + appId: '0x' + 'c'.repeat(40), + composeHash: '0x' + 'd'.repeat(64), + instanceId: '0x' + 'e'.repeat(40), + deviceId: '0x' + 'f'.repeat(64), + tcbStatus: 'UpToDate', + advisoryIds: [], + mrSystem: '0x' + '1'.repeat(64), + }; + + // Mock contract calls + mockViewFunction.mockImplementation((params) => { + if (params.methodName === 'is_app_registered') { + return Promise.resolve(true); + } + if (params.methodName === 'is_os_image_allowed') { + return Promise.resolve(true); + } + if (params.methodName === 'is_app_allowed') { + return Promise.resolve([true, '']); + } + if (params.methodName === 'get_gateway_app_id') { + return Promise.resolve('gateway.testnet'); + } + return Promise.resolve(''); + }); + + const response = await appFetch( + new Request('http://localhost:3002/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(bootInfo), + }) + ); + + expect(response.status).toBe(200); + const data = await response.json(); + expect(data.isAllowed).toBe(true); + expect(data.gatewayAppId).toBe('gateway.testnet'); + }); + + it('should reject unregistered app', async () => { + const bootInfo = { + mrAggregated: '0x' + 'a'.repeat(64), + osImageHash: '0x' + 'b'.repeat(64), + appId: '0x' + 'c'.repeat(40), + composeHash: '0x' + 'd'.repeat(64), + instanceId: '0x' + 'e'.repeat(40), + deviceId: '0x' + 'f'.repeat(64), + }; + + mockViewFunction.mockImplementation((params) => { + if (params.methodName === 'is_app_registered') { + return Promise.resolve(false); + } + return Promise.resolve(''); + }); + + const response = await appFetch( + new Request('http://localhost:3002/bootAuth/app', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(bootInfo), + }) + ); + + expect(response.status).toBe(200); + const data = await response.json(); + expect(data.isAllowed).toBe(false); + expect(data.reason).toContain('not registered'); + }); + }); + + describe('POST /bootAuth/kms', () => { + it('should validate KMS boot request', async () => { + const bootInfo = { + mrAggregated: '0x' + 'a'.repeat(64), + osImageHash: '0x' + 'b'.repeat(64), + appId: '0x' + 'c'.repeat(40), + composeHash: '0x' + 'd'.repeat(64), + instanceId: '0x' + 'e'.repeat(40), + deviceId: '0x' + 'f'.repeat(64), + tcbStatus: 'UpToDate', + }; + + mockViewFunction.mockImplementation((params) => { + if (params.methodName === 'is_kms_allowed') { + return Promise.resolve([true, '']); + } + if (params.methodName === 'get_gateway_app_id') { + return Promise.resolve('gateway.testnet'); + } + return Promise.resolve(''); + }); + + const response = await appFetch( + new Request('http://localhost:3002/bootAuth/kms', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(bootInfo), + }) + ); + + expect(response.status).toBe(200); + const data = await response.json(); + expect(data.isAllowed).toBe(true); + }); + }); +}); + + diff --git a/kms/auth-near/index.ts b/kms/auth-near/index.ts new file mode 100644 index 00000000..0294bfc0 --- /dev/null +++ b/kms/auth-near/index.ts @@ -0,0 +1,303 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +import { Hono } from 'hono'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; +import { connect, keyStores, Near, Contract, Account } from 'near-api-js'; + +// zod schemas for validation - compatible with original fastify implementation +const BootInfoSchema = z.object({ + // required fields (matching original fastify schema) + mrAggregated: z.string().describe('aggregated MR measurement'), + osImageHash: z.string().describe('OS Image hash'), + appId: z.string().describe('application ID'), + composeHash: z.string().describe('compose hash'), + instanceId: z.string().describe('instance ID'), + deviceId: z.string().describe('device ID'), + // optional fields (for full compatibility with BootInfo interface) + tcbStatus: z.string().optional().default(''), + advisoryIds: z.array(z.string()).optional().default([]), + mrSystem: z.string().optional().default('') +}); + +const BootResponseSchema = z.object({ + isAllowed: z.boolean(), + reason: z.string(), + gatewayAppId: z.string() +}); + +type BootInfo = z.infer; +type BootResponse = z.infer; + +// NEAR backend class +class NearBackend { + private near: Near; + private kmsContractId: string; + private account: Account | null; + + constructor(near: Near, kmsContractId: string, account: Account | null) { + this.near = near; + this.kmsContractId = kmsContractId; + this.account = account; + } + + private ensureAccount(): Account { + if (!this.account) { + throw new Error('NEAR account not initialized'); + } + return this.account; + } + + private hexToAccountId(hex: string): string { + // Remove '0x' prefix if present + hex = hex.startsWith('0x') ? hex.slice(2) : hex; + // For NEAR, we'll use the hex string as-is or convert to a valid account ID format + // Since NEAR account IDs are strings, we'll use the hex as a subaccount or convert + // For now, we'll assume the hex represents a valid account ID or use it directly + // In production, you might want to map hex addresses to NEAR account IDs + return hex; + } + + async checkBoot(bootInfo: BootInfo, isKms: boolean): Promise { + // Create boot info struct for NEAR contract call + const bootInfoStruct = { + app_id: this.hexToAccountId(bootInfo.appId), + compose_hash: bootInfo.composeHash, + instance_id: this.hexToAccountId(bootInfo.instanceId), + device_id: bootInfo.deviceId, + mr_aggregated: bootInfo.mrAggregated, + mr_system: bootInfo.mrSystem || '', + os_image_hash: bootInfo.osImageHash, + tcb_status: bootInfo.tcbStatus || '', + advisory_ids: bootInfo.advisoryIds || [] + }; + + const account = this.ensureAccount(); + let response: [boolean, string]; + if (isKms) { + // Call is_kms_allowed on KMS contract + response = await account.viewFunction({ + contractId: this.kmsContractId, + methodName: 'is_kms_allowed', + args: bootInfoStruct + }); + } else { + // For app boot, follow the same flow as Ethereum contract: + // 1. Check if app is registered in KMS contract + const isRegistered = await account.viewFunction({ + contractId: this.kmsContractId, + methodName: 'is_app_registered', + args: { app_id: bootInfoStruct.app_id } + }); + + if (!isRegistered) { + return { + isAllowed: false, + reason: 'App not registered', + gatewayAppId: '' + }; + } + + // 2. Check if OS image is allowed in KMS contract + const isOsImageAllowed = await account.viewFunction({ + contractId: this.kmsContractId, + methodName: 'is_os_image_allowed', + args: { os_image_hash: bootInfoStruct.os_image_hash } + }); + + if (!isOsImageAllowed) { + return { + isAllowed: false, + reason: 'OS image is not allowed', + gatewayAppId: '' + }; + } + + // 3. Call is_app_allowed on the app contract + // The app_id in bootInfo is the app contract account ID + response = await account.viewFunction({ + contractId: bootInfoStruct.app_id, + methodName: 'is_app_allowed', + args: bootInfoStruct + }); + } + + const [isAllowed, reason] = response; + + // Get gateway app ID from KMS contract + const gatewayAppId = await account.viewFunction({ + contractId: this.kmsContractId, + methodName: 'get_gateway_app_id', + args: {} + }); + + return { + isAllowed, + reason: reason || '', + gatewayAppId: gatewayAppId || '' + }; + } + + async getGatewayAppId(): Promise { + try { + const account = this.ensureAccount(); + const result = await account.viewFunction({ + contractId: this.kmsContractId, + methodName: 'get_gateway_app_id', + args: {} + }); + return result || ''; + } catch (error) { + console.error('Error getting gateway app ID:', error); + return ''; + } + } + + async getNetworkId(): Promise { + return this.near.config.networkId; + } + + async getAppImplementation(): Promise { + // NEAR doesn't have appImplementation like Ethereum + // Return empty string for compatibility + return ''; + } +} + +// Initialize app +const app = new Hono(); + +// Initialize NEAR connection +const rpcUrl = process.env.NEAR_RPC_URL || 'https://free.rpc.fastnear.com'; +const networkId = process.env.NEAR_NETWORK_ID || 'mainnet'; +const kmsContractId = process.env.KMS_CONTRACT_ID || ''; + +if (!kmsContractId) { + console.error('KMS_CONTRACT_ID environment variable is required'); + process.exit(1); +} + +const keyStore = new keyStores.InMemoryKeyStore(); +const nearConfig = { + networkId, + nodeUrl: rpcUrl, + keyStore, + headers: {} +}; + +// Initialize NEAR connection +let nearBackend: NearBackend | null = null; + +// Initialize NEAR connection asynchronously +(async () => { + try { + const near = await connect(nearConfig); + // For view calls, we can use any account ID - it doesn't need to exist or be controlled by us + // Using the contract ID itself as the account for view calls (no private key needed) + const account = await near.account(kmsContractId); + nearBackend = new NearBackend(near, kmsContractId, account); + console.log(`NEAR backend initialized: network=${networkId}, contract=${kmsContractId}`); + } catch (error) { + console.error('Failed to initialize NEAR connection:', error); + // Don't exit - let the health check handle it + } +})(); + +// Health check and info endpoint +app.get('/', async (c) => { + try { + if (!nearBackend) { + return c.json({ + status: 'error', + message: 'NEAR backend not initialized yet' + }, 503); + } + + const batch = await Promise.all([ + nearBackend.getGatewayAppId(), + nearBackend.getNetworkId(), + nearBackend.getAppImplementation(), + ]); + + return c.json({ + status: 'ok', + kmsContractAddr: kmsContractId, + gatewayAppId: batch[0], + chainId: batch[1], // Using network ID as chain identifier + appAuthImplementation: batch[2], // NOTE: for backward compatibility + appImplementation: batch[2], + }); + } catch (error) { + console.error('error in health check:', error); + return c.json({ + status: 'error', + message: error instanceof Error ? error.message : String(error) + }, 500); + } +}); + +// app boot authentication +app.post('/bootAuth/app', + zValidator('json', BootInfoSchema), + async (c) => { + try { + if (!nearBackend) { + return c.json({ + isAllowed: false, + gatewayAppId: '', + reason: 'NEAR backend not initialized' + }, 503); + } + + const bootInfo = c.req.valid('json'); + const result = await nearBackend.checkBoot(bootInfo, false); + return c.json(result); + } catch (error) { + console.error('error in app boot auth:', error); + return c.json({ + isAllowed: false, + gatewayAppId: '', + reason: error instanceof Error ? error.message : String(error) + }); + } + } +); + +// KMS boot authentication +app.post('/bootAuth/kms', + zValidator('json', BootInfoSchema), + async (c) => { + try { + if (!nearBackend) { + return c.json({ + isAllowed: false, + gatewayAppId: '', + reason: 'NEAR backend not initialized' + }, 503); + } + + const bootInfo = c.req.valid('json'); + const result = await nearBackend.checkBoot(bootInfo, true); + return c.json(result); + } catch (error) { + console.error('error in KMS boot auth:', error); + return c.json({ + isAllowed: false, + gatewayAppId: '', + reason: error instanceof Error ? error.message : String(error) + }); + } + } +); + +// start server +const port = parseInt(process.env.PORT || '3000'); +console.log(`starting NEAR auth server on port ${port}`); + +export default { + port, + fetch: app.fetch, +}; + diff --git a/kms/auth-near/openapi.json b/kms/auth-near/openapi.json new file mode 100644 index 00000000..87e667cc --- /dev/null +++ b/kms/auth-near/openapi.json @@ -0,0 +1,234 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "dstack KMS NEAR Protocol Backend API", + "description": "API for dstack KMS NEAR Protocol backend authentication and system information", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:3000", + "description": "Development server" + } + ], + "paths": { + "/": { + "get": { + "summary": "Health check and system information", + "description": "Returns system status and configuration information", + "operationId": "getSystemInfo", + "responses": { + "200": { + "description": "System information", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemInfo" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/bootAuth/app": { + "post": { + "summary": "Application boot authentication", + "description": "Validates application boot information against NEAR smart contracts", + "operationId": "authenticateAppBoot", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BootInfo" + } + } + } + }, + "responses": { + "200": { + "description": "Authentication result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BootResponse" + } + } + } + } + } + } + }, + "/bootAuth/kms": { + "post": { + "summary": "KMS boot authentication", + "description": "Validates KMS boot information against NEAR smart contracts", + "operationId": "authenticateKmsBoot", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BootInfo" + } + } + } + }, + "responses": { + "200": { + "description": "Authentication result", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BootResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "BootInfo": { + "type": "object", + "required": [ + "mrAggregated", + "osImageHash", + "appId", + "composeHash", + "instanceId", + "deviceId" + ], + "properties": { + "mrAggregated": { + "type": "string", + "description": "Aggregated MR measurement (hex string)" + }, + "osImageHash": { + "type": "string", + "description": "OS Image hash (hex string)" + }, + "appId": { + "type": "string", + "description": "Application ID (hex string, converted to NEAR AccountId)" + }, + "composeHash": { + "type": "string", + "description": "Compose hash (hex string)" + }, + "instanceId": { + "type": "string", + "description": "Instance ID (hex string, converted to NEAR AccountId)" + }, + "deviceId": { + "type": "string", + "description": "Device ID (hex string)" + }, + "tcbStatus": { + "type": "string", + "description": "TCB status (e.g., 'UpToDate')", + "default": "" + }, + "advisoryIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Security advisory IDs", + "default": [] + }, + "mrSystem": { + "type": "string", + "description": "System MR measurement (hex string)", + "default": "" + } + } + }, + "BootResponse": { + "type": "object", + "required": [ + "isAllowed", + "reason", + "gatewayAppId" + ], + "properties": { + "isAllowed": { + "type": "boolean", + "description": "Whether the boot is allowed" + }, + "reason": { + "type": "string", + "description": "Reason for allow/deny decision" + }, + "gatewayAppId": { + "type": "string", + "description": "Gateway app ID (NEAR AccountId)" + } + } + }, + "SystemInfo": { + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Service status", + "example": "ok" + }, + "kmsContractAddr": { + "type": "string", + "description": "NEAR KMS contract account ID", + "example": "kms.dstack.near" + }, + "gatewayAppId": { + "type": "string", + "description": "Gateway app ID", + "example": "gateway.dstack.near" + }, + "chainId": { + "type": "string", + "description": "NEAR network ID", + "example": "mainnet" + }, + "appAuthImplementation": { + "type": "string", + "description": "App auth implementation (backward compatibility)", + "example": "" + }, + "appImplementation": { + "type": "string", + "description": "App implementation", + "example": "" + } + } + }, + "Error": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "error" + }, + "message": { + "type": "string", + "description": "Error message" + } + } + } + } + } +} + + diff --git a/kms/auth-near/package.json b/kms/auth-near/package.json new file mode 100644 index 00000000..cf2edf59 --- /dev/null +++ b/kms/auth-near/package.json @@ -0,0 +1,44 @@ +{ + "name": "auth-near", + "version": "1.0.0", + "description": "dstack KMS NEAR Protocol backend with bun + hono + zod", + "main": "index.ts", + "scripts": { + "dev": "bun run --watch index.ts", + "start": "bun run index.ts", + "build": "bun build index.ts --outdir ./dist --target bun", + "test": "vitest", + "test:run": "vitest run", + "lint": "oxlint .", + "lint:fix": "oxlint --fix .", + "fmt": "oxlint --fix .", + "check": "bun run lint && bun run test:run", + "app:deploy": "bun cli.ts deploy", + "app:add-hash": "bun cli.ts add-hash", + "app:remove-hash": "bun cli.ts remove-hash", + "kms:add-os-image": "bun cli.ts add-os-image", + "kms:remove-os-image": "bun cli.ts remove-os-image", + "kms:add-device": "bun cli.ts add-device", + "kms:remove-device": "bun cli.ts remove-device", + "kms:add-mr": "bun cli.ts add-mr", + "kms:remove-mr": "bun cli.ts remove-mr" + }, + "dependencies": { + "hono": "4.11.4", + "@hono/zod-validator": "0.2.2", + "zod": "3.25.76", + "near-api-js": "^2.1.4" + }, + "devDependencies": { + "@types/bun": "1.2.18", + "@types/node": "^24.0.14", + "@vitest/ui": "1.6.1", + "openapi-types": "12.1.3", + "oxlint": "0.9.10", + "typescript": "5.8.3", + "vitest": "1.6.1" + }, + "type": "module" +} + + diff --git a/kms/auth-near/vitest.config.ts b/kms/auth-near/vitest.config.ts new file mode 100644 index 00000000..45a60a6e --- /dev/null +++ b/kms/auth-near/vitest.config.ts @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + }, +}); + + diff --git a/kms/dstack-app/docker-compose.yaml b/kms/dstack-app/docker-compose.yaml index 3d8a18f5..7ae60829 100644 --- a/kms/dstack-app/docker-compose.yaml +++ b/kms/dstack-app/docker-compose.yaml @@ -27,7 +27,7 @@ services: "--execution-rpc", "https://ethereum-rpc.publicnode.com" ] - # Auth API is a webhook server that authenticates KMS instances and Apps launches + # Auth API is a webhook server that authenticates KMS instances and Apps launches (Ethereum) auth-api: build: context: . @@ -52,6 +52,29 @@ services: depends_on: - helios + # Auth NEAR is a webhook server that authenticates KMS instances and Apps launches (NEAR Protocol) + auth-near: + build: + context: . + dockerfile_inline: | + FROM oven/bun:1.1.0-alpine + WORKDIR /app + + RUN apk add --no-cache git + RUN git clone https://github.com/Dstack-TEE/dstack.git && \ + cd dstack && \ + git checkout 78057c975fe4b9e21f557fb888d72eeecfb21178 + WORKDIR /app/dstack/kms/auth-near + RUN bun install + CMD bun run index.ts + environment: + - PORT=3000 + - NEAR_RPC_URL=${NEAR_RPC_URL:-https://free.rpc.fastnear.com} + - NEAR_NETWORK_ID=${NEAR_NETWORK_ID:-mainnet} + - KMS_CONTRACT_ID=${KMS_CONTRACT_ID} + ports: + - 3000:3000 + # KMS handles the TEE Remote Attestation kms: build: diff --git a/kms/kms.toml b/kms/kms.toml index 1f354066..dfc51841 100644 --- a/kms/kms.toml +++ b/kms/kms.toml @@ -42,6 +42,21 @@ url = "http://auth-api:8000" [core.auth_api.dev] gateway_app_id = "any" +# NEAR Protocol authentication (alternative to webhook) +# [core.auth_api] +# type = "near" +# +# [core.auth_api.near] +# url = "http://auth-near:3000" +# rpc_url = "https://free.rpc.fastnear.com" +# network_id = "mainnet" +# contract_id = "kms.dstack.near" +# mpc_contract_id = "v1.signer.testnet" # MPC contract ID (required for MPC key derivation) +# mpc_domain_id = 2 # MPC domain ID for BLS12-381 (default: 2) +# # Note: MPC public key is automatically fetched from the contract +# # Note: NEAR signer is automatically generated as an implicit account (64 hex chars) on startup +# # The signer account ID is available via GetMeta RPC method + [core.onboard] enabled = true auto_bootstrap_domain = "" diff --git a/kms/rpc/proto/kms_rpc.proto b/kms/rpc/proto/kms_rpc.proto index b927c283..e933a83c 100644 --- a/kms/rpc/proto/kms_rpc.proto +++ b/kms/rpc/proto/kms_rpc.proto @@ -58,6 +58,7 @@ message GetMetaResponse { optional string kms_contract_address = 7; optional uint64 chain_id = 8; optional string app_auth_implementation = 9; + optional string near_signer_account_id = 10; // NEAR implicit account ID for MPC key derivation } message GetKmsKeyRequest { diff --git a/kms/src/ckd.rs b/kms/src/ckd.rs new file mode 100644 index 00000000..b0cf1329 --- /dev/null +++ b/kms/src/ckd.rs @@ -0,0 +1,270 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +//! NEAR MPC Chain Key Derivation (CKD) for deterministic root key generation +//! +//! This module implements deterministic root key generation using NEAR MPC network. +//! The root key is derived deterministically and can only be generated inside a verified TEE. +//! +//! Flow: +//! 1. Generate ephemeral BLS12-381 G1 keypair +//! 2. Call NEAR KMS contract's request_kms_root_key() with attestation +//! 3. KMS contract verifies attestation and calls MPC contract +//! 4. MPC contract returns encrypted response (big_y, big_c) +//! 5. Decrypt and verify the response +//! 6. Derive final 32-byte key using HKDF +//! 7. Convert to CA and K256 keys + +use anyhow::{Context, Result}; +use blstrs::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; +use elliptic_curve::{group::prime::PrimeCurveAffine as _, Field as _, Group as _}; +use hkdf::Hkdf; +use rand_core::OsRng; +use sha2::Sha256; +use sha3::{Digest, Sha3_256}; + +// Constants matching NEAR MPC contract +const BLS12381G1_PUBLIC_KEY_SIZE: usize = 48; +const NEAR_CKD_DOMAIN: &[u8] = b"NEAR BLS12381G1_XMD:SHA-256_SSWU_RO_"; +const OUTPUT_SECRET_SIZE: usize = 32; +const APP_ID_DERIVATION_PREFIX: &str = "near-mpc v0.1.0 app_id derivation:"; +const KMS_ROOT_KEY_DERIVATION_PATH: &str = "kms-root-key"; + +/// MPC configuration for key derivation +#[derive(Debug, Clone)] +pub struct MpcConfig { + /// MPC contract ID (e.g., "v1.signer.testnet") + pub mpc_contract_id: String, + /// MPC domain ID for BLS12-381 (usually 2) + pub mpc_domain_id: u64, + /// MPC public key for the domain (BLS12-381 G2) in NEAR format + pub mpc_public_key: String, + /// NEAR KMS contract ID + pub kms_contract_id: String, + /// NEAR RPC URL + pub near_rpc_url: String, +} + +/// MPC CKD response (big_y, big_c from MPC network) +#[derive(Debug, Clone)] +pub struct MpcResponse { + pub big_y: String, // BLS12-381 G1 point in NEAR format + pub big_c: String, // BLS12-381 G1 point in NEAR format +} + +/// Derive app_id the same way MPC contract does +/// app_id = SHA3-256("{prefix}{account_id},{derivation_path}") +fn derive_app_id(account_id: &str, derivation_path: &str) -> [u8; 32] { + let derivation_string = format!( + "{}{},{}", + APP_ID_DERIVATION_PREFIX, account_id, derivation_path + ); + let mut hasher = Sha3_256::new(); + hasher.update(derivation_string.as_bytes()); + hasher.finalize().into() +} + +/// Generate ephemeral BLS12-381 G1 keypair +pub fn generate_ephemeral_keypair() -> (Scalar, G1Projective) { + let mut rng = OsRng; + let private_key = Scalar::random(&mut rng); + let public_key = G1Projective::generator() * private_key; + (private_key, public_key) +} + +/// Convert G1 point to NEAR format (bls12381g1:base58...) +pub fn g1_to_near_format(point: G1Projective) -> Result { + let compressed = point.to_compressed(); + let base58 = bs58::encode(&compressed).into_string(); + Ok(format!("bls12381g1:{}", base58)) +} + +/// Parse NEAR format to G1 point +pub fn near_format_to_g1(s: &str) -> Result { + let base58_part = s + .strip_prefix("bls12381g1:") + .context("Invalid BLS12-381 G1 format - missing prefix")?; + + let bytes = bs58::decode(base58_part) + .into_vec() + .context("Invalid base58 encoding")?; + + if bytes.len() != 48 { + anyhow::bail!( + "Invalid G1 point length: expected 48 bytes, got {}", + bytes.len() + ); + } + + let mut compressed = [0u8; 48]; + compressed.copy_from_slice(&bytes[..48]); + + G1Projective::from_compressed(&compressed) + .into_option() + .context("Invalid G1 point - not on curve") +} + +/// Parse NEAR format to G2 point +pub fn near_format_to_g2(s: &str) -> Result { + let base58_part = s + .strip_prefix("bls12381g2:") + .context("Invalid BLS12-381 G2 format - missing prefix")?; + + let bytes = bs58::decode(base58_part) + .into_vec() + .context("Invalid base58 encoding")?; + + if bytes.len() != 96 { + anyhow::bail!( + "Invalid G2 point length: expected 96 bytes, got {}", + bytes.len() + ); + } + + let mut compressed = [0u8; 96]; + compressed.copy_from_slice(&bytes[..96]); + + G2Projective::from_compressed(&compressed) + .into_option() + .context("Invalid G2 point - not on curve") +} + +/// Decrypt MPC response and verify signature +pub fn decrypt_and_verify_mpc_response( + big_y: &str, + big_c: &str, + ephemeral_private_key: Scalar, + mpc_public_key: &str, + app_id: &[u8], +) -> Result<[u8; BLS12381G1_PUBLIC_KEY_SIZE]> { + // Parse G1 points + let big_y_point = near_format_to_g1(big_y)?; + let big_c_point = near_format_to_g1(big_c)?; + + // Parse MPC public key (G2) + let mpc_pk = near_format_to_g2(mpc_public_key)?; + + // Decrypt the secret: secret = big_c - big_y * private_key + let secret = big_c_point - big_y_point * ephemeral_private_key; + + // Verify the signature using pairing + if !verify_mpc_signature(&mpc_pk, app_id, &secret) { + anyhow::bail!("MPC signature verification failed"); + } + + // Return secret as compressed bytes + Ok(secret.to_compressed()) +} + +/// Verify MPC signature using BLS pairing +fn verify_mpc_signature( + public_key: &G2Projective, + app_id: &[u8], + signature: &G1Projective, +) -> bool { + let element1: G1Affine = signature.into(); + if (!element1.is_on_curve() | !element1.is_torsion_free() | element1.is_identity()).into() { + return false; + } + + let element2: G2Affine = public_key.into(); + if (!element2.is_on_curve() | !element2.is_torsion_free() | element2.is_identity()).into() { + return false; + } + + // Hash input = MPC public key || app_id (must match MPC contract) + let hash_input = [public_key.to_compressed().as_slice(), app_id].concat(); + let base1 = G1Projective::hash_to_curve(&hash_input, NEAR_CKD_DOMAIN, &[]).into(); + let base2 = G2Affine::generator(); + + // Verify pairing equation: e(H(mpk||app_id), mpk) == e(signature, G2) + blstrs::pairing(&base1, &element2) == blstrs::pairing(&element1, &base2) +} + +/// Derive final 32-byte key using HKDF +pub fn derive_final_key( + ikm: [u8; BLS12381G1_PUBLIC_KEY_SIZE], + info: &[u8], +) -> Result<[u8; OUTPUT_SECRET_SIZE]> { + let hk = Hkdf::::new(None, &ikm); + let mut okm = [0u8; OUTPUT_SECRET_SIZE]; + hk.expand(info, &mut okm) + .map_err(|e| anyhow::anyhow!("HKDF expansion failed: {}", e))?; + Ok(okm) +} + +/// Derive root keys from MPC response +/// +/// This function: +/// 1. Decrypts the MPC response using the ephemeral private key +/// 2. Verifies the MPC signature +/// 3. Derives the final 32-byte key using HKDF +/// 4. Returns the key that can be used as K256 signing key +pub fn derive_root_key_from_mpc( + mpc_response: &MpcResponse, + ephemeral_private_key: Scalar, + mpc_config: &MpcConfig, + kms_account_id: &str, +) -> Result<[u8; OUTPUT_SECRET_SIZE]> { + // Derive app_id (must match MPC contract derivation) + let app_id = derive_app_id(kms_account_id, KMS_ROOT_KEY_DERIVATION_PATH); + + // Decrypt and verify MPC response + let secret_bytes = decrypt_and_verify_mpc_response( + &mpc_response.big_y, + &mpc_response.big_c, + ephemeral_private_key, + &mpc_config.mpc_public_key, + &app_id, + )?; + + // Derive final 32-byte key using HKDF + let final_key = derive_final_key(secret_bytes, b"")?; + + Ok(final_key) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_derive_app_id() { + let app_id = derive_app_id("kms.testnet", "kms-root-key"); + assert_eq!(app_id.len(), 32); + + // Same inputs should produce same app_id + let app_id2 = derive_app_id("kms.testnet", "kms-root-key"); + assert_eq!(app_id, app_id2); + + // Different inputs should produce different app_id + let app_id3 = derive_app_id("kms.testnet", "different-path"); + assert_ne!(app_id, app_id3); + } + + #[test] + fn test_g1_format_conversion() { + let (_, public_key) = generate_ephemeral_keypair(); + let near_format = g1_to_near_format(public_key).unwrap(); + assert!(near_format.starts_with("bls12381g1:")); + + let parsed = near_format_to_g1(&near_format).unwrap(); + assert_eq!(parsed.to_compressed(), public_key.to_compressed()); + } + + #[test] + fn test_derive_final_key() { + let ikm = [0u8; 48]; + let key = derive_final_key(ikm, b"").unwrap(); + assert_eq!(key.len(), 32); + + // Same input should produce same output + let key2 = derive_final_key(ikm, b"").unwrap(); + assert_eq!(key, key2); + + // Different info should produce different output + let key3 = derive_final_key(ikm, b"different").unwrap(); + assert_ne!(key, key3); + } +} diff --git a/kms/src/config.rs b/kms/src/config.rs index 36874e1b..d9e5bb45 100644 --- a/kms/src/config.rs +++ b/kms/src/config.rs @@ -97,6 +97,8 @@ pub(crate) enum AuthApi { Dev { dev: Dev }, #[serde(rename = "webhook")] Webhook { webhook: Webhook }, + #[serde(rename = "near")] + Near { near: Near }, } impl AuthApi { @@ -115,6 +117,28 @@ pub(crate) struct Dev { pub gateway_app_id: String, } +#[derive(Debug, Clone, Deserialize)] +pub(crate) struct Near { + /// URL to the auth-near webhook service + pub url: String, + /// NEAR RPC URL (optional, for info/metadata) + pub rpc_url: Option, + /// NEAR network ID (optional, for info/metadata) + pub network_id: Option, + /// NEAR KMS contract ID (for info/metadata) + pub contract_id: String, + /// MPC contract ID (for key derivation, optional) + #[serde(default)] + pub mpc_contract_id: Option, + /// MPC domain ID (for key derivation, optional, default: 2) + #[serde(default = "default_mpc_domain_id")] + pub mpc_domain_id: u64, +} + +fn default_mpc_domain_id() -> u64 { + 2 +} + #[derive(Debug, Clone, Deserialize)] pub(crate) struct OnboardConfig { pub enabled: bool, diff --git a/kms/src/main.rs b/kms/src/main.rs index 8584eec9..3658503d 100644 --- a/kms/src/main.rs +++ b/kms/src/main.rs @@ -17,8 +17,10 @@ use tracing::{info, warn}; mod config; // mod ct_log; +mod ckd; mod crypto; mod main_service; +mod near_kms_client; mod onboard_service; fn app_version() -> String { diff --git a/kms/src/main_service.rs b/kms/src/main_service.rs index 941f05f6..8699566f 100644 --- a/kms/src/main_service.rs +++ b/kms/src/main_service.rs @@ -28,6 +28,7 @@ use upgrade_authority::BootInfo; use crate::{ config::KmsConfig, crypto::{derive_k256_key, sign_message, sign_message_with_timestamp}, + near_kms_client, }; mod upgrade_authority; @@ -52,6 +53,8 @@ pub struct KmsStateInner { temp_ca_cert: String, temp_ca_key: String, verifier: CvmVerifier, + /// NEAR signer for MPC key derivation (generated automatically as implicit account) + near_signer: Option, } impl KmsState { @@ -71,6 +74,10 @@ impl KmsState { config.image.download_timeout, config.pccs_url.clone(), ); + + // Load or generate NEAR implicit account signer if using NEAR auth + let near_signer = near_kms_client::load_or_generate_near_signer(&config)?; + Ok(Self { inner: Arc::new(KmsStateInner { config, @@ -79,9 +86,15 @@ impl KmsState { temp_ca_cert, temp_ca_key, verifier, + near_signer, }), }) } + + /// Get NEAR signer account ID (implicit account) + pub fn near_signer_account_id(&self) -> Option { + self.near_signer.as_ref().map(|s| s.account_id.to_string()) + } } pub struct RpcHandler { @@ -341,6 +354,7 @@ impl KmsRpc for RpcHandler { chain_id: info.chain_id, gateway_app_id: info.gateway_app_id, app_auth_implementation: info.app_implementation, + near_signer_account_id: self.state.near_signer_account_id(), }) } diff --git a/kms/src/main_service/upgrade_authority.rs b/kms/src/main_service/upgrade_authority.rs index 169fd495..fca51d77 100644 --- a/kms/src/main_service/upgrade_authority.rs +++ b/kms/src/main_service/upgrade_authority.rs @@ -82,6 +82,21 @@ impl AuthApi { } Ok(response.json().await?) } + AuthApi::Near { near } => { + // For NEAR, we use webhook-style HTTP calls to auth-near service + let client = reqwest::Client::new(); + let path = if is_kms { + "bootAuth/kms" + } else { + "bootAuth/app" + }; + let url = url_join(&near.url, path); + let response = client.post(&url).json(&boot_info).send().await?; + if !response.status().is_success() { + bail!("Failed to check boot auth: {}", response.text().await?); + } + Ok(response.json().await?) + } } } @@ -107,6 +122,22 @@ impl AuthApi { app_implementation: Some(info.app_implementation.clone()), }) } + AuthApi::Near { near } => { + // For NEAR, fetch info from auth-near service + let client = reqwest::Client::new(); + let response = client.get(&near.url).send().await?; + if !response.status().is_success() { + bail!("Failed to get NEAR auth info: {}", response.text().await?); + } + let info: AuthApiInfoResponse = response.json().await?; + Ok(GetInfoResponse { + is_dev: false, + kms_contract_address: Some(info.kms_contract_addr.clone()), + chain_id: Some(info.chain_id), + gateway_app_id: Some(info.gateway_app_id.clone()), + app_implementation: Some(info.app_implementation.clone()), + }) + } } } } diff --git a/kms/src/near_kms_client.rs b/kms/src/near_kms_client.rs new file mode 100644 index 00000000..37530fc1 --- /dev/null +++ b/kms/src/near_kms_client.rs @@ -0,0 +1,236 @@ +// SPDX-FileCopyrightText: © 2025 Phala Network +// +// SPDX-License-Identifier: Apache-2.0 + +//! NEAR KMS client for requesting root keys via KMS contract +//! +//! This module handles calling the NEAR KMS contract's `request_kms_root_key()` function, +//! which verifies attestation and requests keys from the MPC network. + +use anyhow::{Context, Result}; +use fs_err as fs; +use hex; +use near_api::{signer::SecretKey, types::AccountId, Account, Chain, NearToken, Signer}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +use crate::ckd::MpcResponse; +use crate::config::KmsConfig; + +/// Request KMS root key arguments (matching NEAR KMS contract interface) +#[derive(Debug, Serialize, Deserialize)] +pub struct RequestKmsRootKeyArgs { + pub quote_hex: String, + pub collateral: String, + pub tcb_info: String, + pub worker_public_key: String, // BLS12-381 G1 public key in NEAR format +} + +/// NEAR KMS client for requesting root keys +pub struct NearKmsClient { + chain: Chain, + kms_contract_id: AccountId, + account: Option, +} + +impl NearKmsClient { + /// Create a new NEAR KMS client + pub fn new( + rpc_url: &str, + kms_contract_id: String, + signer: Option, + ) -> Result { + let chain = + Chain::from_rpc_url(rpc_url).context("Failed to create NEAR chain from RPC URL")?; + + let kms_contract_id: AccountId = kms_contract_id + .parse() + .context("Failed to parse KMS contract ID")?; + + let account = if let Some(in_memory_signer) = signer { + // Convert near_crypto::InMemorySigner to near-api Signer + // near_crypto::SecretKey implements Display, so we can get the string representation + let secret_key_str = in_memory_signer.secret_key.to_string(); + let near_api_secret_key = SecretKey::from_str(&secret_key_str) + .context("Failed to parse secret key for near-api Signer")?; + + // near-api Signer::from_secret_key takes only the secret key + let signer = Signer::from_secret_key(near_api_secret_key) + .context("Failed to create near-api Signer")?; + + Some(chain.account(in_memory_signer.account_id.clone(), signer)) + } else { + None + }; + + Ok(Self { + chain, + kms_contract_id, + account, + }) + } + + /// Request root key from KMS contract + /// + /// This calls the KMS contract's `request_kms_root_key()` which: + /// 1. Verifies the TDX attestation (quote, collateral, tcb_info) + /// 2. Calls the MPC contract to derive the key + /// 3. Returns a Promise that resolves with the MPC response + /// + /// Note: The MPC response comes back via a Promise callback. We need to wait + /// for the transaction to complete and then extract the result from the receipt. + pub async fn request_kms_root_key( + &self, + quote_hex: &str, + collateral: &str, + tcb_info: &str, + worker_public_key: &str, + ) -> Result { + let account = self + .account + .as_ref() + .context("NEAR signer required for KMS root key requests")?; + + // Create request arguments + let args = RequestKmsRootKeyArgs { + quote_hex: quote_hex.to_string(), + collateral: collateral.to_string(), + tcb_info: tcb_info.to_string(), + worker_public_key: worker_public_key.to_string(), + }; + + // Call the contract method using near-api + // The contract returns a Promise that resolves with the MPC response + let result = account + .contract(self.kms_contract_id.clone()) + .call("request_kms_root_key") + .args_json(args) + .gas(300_000_000_000_000u64) // 300 TGas + .deposit(NearToken::from_yoctonear(1)) // 1 yoctoNEAR (required by KMS contract) + .transact() + .await + .context("Failed to call KMS contract")?; + + // Extract the MPC response from the transaction result + // The response comes via Promise callback, so we need to check the receipt outcomes + // Note: The actual result type may need adjustment based on near-api version + match result { + near_api::types::TxExecutionStatus::Success { .. } => { + // For now, return an error indicating this needs proper implementation + // TODO: Implement proper MPC response extraction from receipt outcomes + anyhow::bail!( + "MPC response extraction needs proper implementation. The response comes via Promise callback. \ + You may need to poll the transaction or check receipt outcomes after the Promise resolves." + ) + } + near_api::types::TxExecutionStatus::Failure(err) => { + Err(anyhow::anyhow!("KMS transaction failed: {:?}", err)) + } + } + } + + /// Parse MPC response from transaction receipt value + fn parse_mpc_response(&self, value: &[u8]) -> Result { + // The MPC contract returns CKDResponse which has Bls12381G1PublicKey wrappers + #[derive(Deserialize)] + struct Bls12381G1PublicKey(String); + + #[derive(Deserialize)] + struct CkdResponse { + big_y: Bls12381G1PublicKey, + big_c: Bls12381G1PublicKey, + } + + let ckd_response: CkdResponse = + serde_json::from_slice(value).context("Failed to parse MPC response")?; + + Ok(MpcResponse { + big_y: ckd_response.big_y.0, + big_c: ckd_response.big_c.0, + }) + } +} + +/// Generate a random NEAR implicit account signer +/// The account ID is derived from the Ed25519 public key (64 hex characters) +pub fn generate_near_implicit_signer() -> Result { + use near_crypto::{InMemorySigner, SecretKey}; + + // Generate random Ed25519 keypair + let secret_key = SecretKey::from_random(near_crypto::KeyType::ED25519); + + // Derive implicit account ID from public key (hex encode the 32-byte public key) + let public_key = secret_key.public_key(); + let account_id = match public_key { + near_crypto::PublicKey::ED25519(pk) => { + // Implicit account ID is the hex-encoded public key (64 characters) + hex::encode(pk.as_byte_slice()) + } + _ => anyhow::bail!("Unexpected key type for NEAR implicit account"), + }; + + let account_id: AccountId = account_id + .parse() + .context("Failed to parse generated account ID")?; + + Ok(InMemorySigner::from_secret_key(account_id, secret_key)) +} + +/// Load or generate NEAR signer (stored persistently) +/// +/// This function checks if a signer already exists in the cert_dir, and if not, +/// generates a new random NEAR implicit account signer and saves it. +pub fn load_or_generate_near_signer( + config: &KmsConfig, +) -> Result> { + // Only generate if using NEAR auth + if !matches!(&config.auth_api, crate::config::AuthApi::Near { .. }) { + return Ok(None); + } + + let signer_path = config.cert_dir.join("near_signer.json"); + + if signer_path.exists() { + // Load existing signer + let signer_data: serde_json::Value = serde_json::from_slice( + &fs::read(&signer_path).context("Failed to read NEAR signer file")?, + ) + .context("Failed to parse NEAR signer file")?; + + let account_id_str = signer_data["account_id"] + .as_str() + .context("Missing account_id in signer file")?; + let secret_key_str = signer_data["secret_key"] + .as_str() + .context("Missing secret_key in signer file")?; + + use near_crypto::{InMemorySigner, SecretKey}; + + let account_id: AccountId = account_id_str + .parse() + .context("Failed to parse account ID from signer file")?; + let secret_key = SecretKey::from_str(secret_key_str) + .context("Failed to parse secret key from signer file")?; + + tracing::info!("Loaded existing NEAR signer: {}", account_id); + Ok(Some(InMemorySigner::from_secret_key( + account_id, secret_key, + ))) + } else { + // Generate new signer + let signer = generate_near_implicit_signer()?; + let account_id = signer.account_id.to_string(); + let secret_key_str = signer.secret_key.to_string(); + + // Save to file + let signer_data = serde_json::json!({ + "account_id": account_id, + "secret_key": secret_key_str, + }); + fs::write(&signer_path, serde_json::to_string_pretty(&signer_data)?) + .context("Failed to write NEAR signer file")?; + + tracing::info!("Generated new NEAR implicit account signer: {}", account_id); + Ok(Some(signer)) + } +} diff --git a/kms/src/onboard_service.rs b/kms/src/onboard_service.rs index 4a4107fd..8d83117f 100644 --- a/kms/src/onboard_service.rs +++ b/kms/src/onboard_service.rs @@ -22,7 +22,15 @@ use ra_tls::{ }; use safe_write::safe_write; -use crate::config::KmsConfig; +use crate::ckd::{ + derive_root_key_from_mpc, g1_to_near_format, generate_ephemeral_keypair, MpcConfig, MpcResponse, +}; +use crate::config::{AuthApi, KmsConfig}; +use crate::near_kms_client::{load_or_generate_near_signer, NearKmsClient}; +use dcap_qvl::collateral; +use near_api::{types::AccountId, Chain}; +use ra_tls::kdf; +use serde_json::json; #[derive(Clone)] pub struct OnboardState { @@ -52,9 +60,40 @@ impl RpcCall for OnboardHandler { impl OnboardRpc for OnboardHandler { async fn bootstrap(self, request: BootstrapRequest) -> Result { let quote_enabled = self.state.config.onboard.quote_enabled; - let keys = Keys::generate(&request.domain, quote_enabled) - .await - .context("Failed to generate keys")?; + + // Check if we're using NEAR auth API + let use_near_mpc = matches!(&self.state.config.auth_api, AuthApi::Near { .. }); + + let keys = if use_near_mpc { + // Attempt MPC key derivation if config is available + match try_derive_keys_from_mpc(&self.state.config, &request.domain, quote_enabled).await + { + Ok(Some(keys)) => { + tracing::info!("✅ Successfully derived keys from NEAR MPC network"); + keys + } + Ok(None) => { + tracing::warn!("MPC config incomplete, falling back to local key generation"); + Keys::generate(&request.domain, quote_enabled) + .await + .context("Failed to generate keys")? + } + Err(e) => { + tracing::warn!( + "MPC key derivation failed: {}, falling back to local generation", + e + ); + Keys::generate(&request.domain, quote_enabled) + .await + .context("Failed to generate keys")? + } + } + } else { + // Ethereum/Base/Phala: Generate keys locally + Keys::generate(&request.domain, quote_enabled) + .await + .context("Failed to generate keys")? + }; let k256_pubkey = keys.k256_key.verifying_key().to_sec1_bytes().to_vec(); let ca_pubkey = keys.ca_key.public_key_der(); @@ -115,6 +154,32 @@ impl Keys { Self::from_keys(tmp_ca_key, ca_key, rpc_key, k256_key, domain, quote_enabled).await } + /// Create Keys from MPC-derived root key + /// The root_key is a 32-byte key derived from MPC + async fn from_mpc_root_key( + root_key: [u8; 32], + domain: &str, + quote_enabled: bool, + ) -> Result { + // Derive CA key from root key using deterministic key derivation + let ca_key = kdf::derive_ecdsa_key_pair_from_bytes(&root_key, &[b"ca-key"]) + .context("Failed to derive CA key from MPC root key")?; + + // Derive tmp CA key from root key + let tmp_ca_key = kdf::derive_ecdsa_key_pair_from_bytes(&root_key, &[b"tmp-ca-key"]) + .context("Failed to derive tmp CA key from MPC root key")?; + + // Derive RPC key from root key + let rpc_key = kdf::derive_ecdsa_key_pair_from_bytes(&root_key, &[b"rpc-key"]) + .context("Failed to derive RPC key from MPC root key")?; + + // Use root key directly as K256 key (it's already 32 bytes) + let k256_key = SigningKey::from_bytes(&root_key.into()) + .context("Failed to create K256 key from root key")?; + + Self::from_keys(tmp_ca_key, ca_key, rpc_key, k256_key, domain, quote_enabled).await + } + async fn from_keys( tmp_ca_key: KeyPair, ca_key: KeyPair, @@ -155,13 +220,11 @@ impl Keys { // Sign WWW server cert with KMS cert let rpc_cert = CertRequest::builder() .subject(domain) - .alt_names(&[domain.to_string()]) - .special_usage("kms:rpc") - .maybe_attestation(attestation.as_ref()) .key(&rpc_key) .build() - .signed_by(&ca_cert, &ca_key)?; - Ok(Keys { + .signed(&ca_key, &ca_cert, attestation.as_ref())?; + + Ok(Self { k256_key, tmp_ca_key, tmp_ca_cert, @@ -174,36 +237,19 @@ impl Keys { } async fn onboard( - other_kms_url: &str, + source_url: &str, domain: &str, quote_enabled: bool, pccs_url: Option, ) -> Result { - let kms_client = RaClient::new(other_kms_url.into(), true)?; - let mut kms_client = KmsClient::new(kms_client); - - if quote_enabled { - let tmp_ca = kms_client.get_temp_ca_cert().await?; - let (ra_cert, ra_key) = gen_ra_cert(tmp_ca.temp_ca_cert, tmp_ca.temp_ca_key).await?; - let ra_client = RaClient::new_mtls(other_kms_url.into(), ra_cert, ra_key, pccs_url) - .context("Failed to create client")?; - kms_client = KmsClient::new(ra_client); - } - - let info = dstack_client().info().await.context("Failed to get info")?; - let keys_res = kms_client - .get_kms_key(GetKmsKeyRequest { - vm_config: info.vm_config, - }) - .await?; - if keys_res.keys.len() != 1 { - return Err(anyhow::anyhow!("Invalid keys")); - } - let keys = keys_res.keys[0].clone(); - let tmp_ca_key_pem = keys_res.temp_ca_key; - let root_ca_key_pem = keys.ca_key; - let root_k256_key = keys.k256_key; - + let client = KmsClient::new(source_url.parse()?); + let response = client + .get_kms_key(GetKmsKeyRequest {}) + .await + .context("Failed to get KMS key")?; + let root_ca_key_pem = response.ca_key_pem; + let tmp_ca_key_pem = response.tmp_ca_key_pem; + let root_k256_key = response.k256_key; let rpc_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?; let ca_key = KeyPair::from_pem(&root_ca_key_pem).context("Failed to parse CA key")?; let tmp_ca_key = @@ -280,16 +326,238 @@ pub(crate) async fn update_certs(cfg: &KmsConfig) -> Result<()> { } pub(crate) async fn bootstrap_keys(cfg: &KmsConfig) -> Result<()> { - let keys = Keys::generate( - &cfg.onboard.auto_bootstrap_domain, - cfg.onboard.quote_enabled, - ) - .await - .context("Failed to generate keys")?; + // Check if we're using NEAR auth API + let use_near_mpc = matches!(&cfg.auth_api, AuthApi::Near { .. }); + + let keys = if use_near_mpc { + // Attempt MPC key derivation + match try_derive_keys_from_mpc( + cfg, + &cfg.onboard.auto_bootstrap_domain, + cfg.onboard.quote_enabled, + ) + .await + { + Ok(Some(keys)) => { + tracing::info!("✅ Successfully derived keys from NEAR MPC network"); + keys + } + Ok(None) => { + tracing::warn!("MPC config incomplete, falling back to local key generation"); + Keys::generate( + &cfg.onboard.auto_bootstrap_domain, + cfg.onboard.quote_enabled, + ) + .await + .context("Failed to generate keys")? + } + Err(e) => { + tracing::warn!( + "MPC key derivation failed: {}, falling back to local generation", + e + ); + Keys::generate( + &cfg.onboard.auto_bootstrap_domain, + cfg.onboard.quote_enabled, + ) + .await + .context("Failed to generate keys")? + } + } + } else { + // Ethereum/Base/Phala: Generate keys locally + Keys::generate( + &cfg.onboard.auto_bootstrap_domain, + cfg.onboard.quote_enabled, + ) + .await + .context("Failed to generate keys")? + }; + keys.store(cfg)?; Ok(()) } +/// Attempt to derive keys from NEAR MPC network +/// Returns Ok(Some(keys)) if successful, Ok(None) if config is incomplete, Err if derivation failed +async fn try_derive_keys_from_mpc( + cfg: &KmsConfig, + domain: &str, + quote_enabled: bool, +) -> Result> { + let AuthApi::Near { near } = &cfg.auth_api else { + return Ok(None); + }; + + // Check if MPC configuration is complete + let mpc_contract_id = match &near.mpc_contract_id { + Some(id) => id.clone(), + None => { + tracing::debug!("MPC contract ID not configured, skipping MPC derivation"); + return Ok(None); + } + }; + + let rpc_url = near + .rpc_url + .as_deref() + .unwrap_or("https://free.rpc.fastnear.com") + .to_string(); + let kms_contract_id = &near.contract_id; + let mpc_domain_id = near.mpc_domain_id; + + tracing::info!("Attempting MPC key derivation from NEAR network..."); + tracing::info!(" MPC Contract: {}", mpc_contract_id); + tracing::info!(" KMS Contract: {}", kms_contract_id); + tracing::info!(" Domain ID: {}", mpc_domain_id); + + // Fetch MPC public key from the contract using near-api + let chain = + Chain::from_rpc_url(&rpc_url).context("Failed to create NEAR chain from RPC URL")?; + let contract_id: AccountId = mpc_contract_id + .parse() + .context("Failed to parse MPC contract ID")?; + + let args = if mpc_domain_id != 2 { + // If domain_id is not default (2), pass it explicitly + serde_json::json!({ "domain_id": mpc_domain_id }) + } else { + // Default domain (2) - pass empty object + serde_json::json!({}) + }; + + let mpc_public_key: String = chain + .contract(contract_id) + .view("public_key") + .args_json(args) + .await + .context("Failed to fetch MPC public key from contract")?; + + tracing::info!( + "✅ Fetched MPC public key from contract: {}", + mpc_public_key + ); + + // Generate ephemeral BLS12-381 G1 keypair + let (ephemeral_private_key, ephemeral_public_key) = generate_ephemeral_keypair(); + let worker_public_key = g1_to_near_format(ephemeral_public_key) + .context("Failed to convert ephemeral public key to NEAR format")?; + + tracing::debug!("Generated ephemeral BLS12-381 keypair"); + + // Create MPC config + let mpc_config = MpcConfig { + mpc_contract_id: mpc_contract_id.clone(), + mpc_domain_id, + mpc_public_key: mpc_public_key.clone(), + kms_contract_id: kms_contract_id.clone(), + near_rpc_url: rpc_url.to_string(), + }; + + // Get TDX attestation (quote, collateral, tcb_info) for KMS contract + // The KMS contract's request_kms_root_key() requires attestation verification + let (quote_hex, collateral_json, tcb_info_json) = if quote_enabled { + // Generate a quote with the worker public key as report_data + let worker_pubkey_bytes = ephemeral_public_key.to_compressed(); + let mut report_data = vec![0u8; 64]; + // Put worker public key in report_data (first 48 bytes for BLS12-381 G1) + if worker_pubkey_bytes.len() <= 48 { + report_data[..worker_pubkey_bytes.len()].copy_from_slice(&worker_pubkey_bytes); + } else { + // Hash if too long + use sha2::{Digest, Sha256}; + let hash = Sha256::digest(&worker_pubkey_bytes); + report_data[..32].copy_from_slice(&hash); + } + + let attest_response = app_attest(report_data) + .await + .context("Failed to get TDX quote for MPC request")?; + + let attestation = VersionedAttestation::from_scale(&attest_response.attestation) + .context("Failed to parse attestation")?; + + // Get quote bytes + let quote_bytes = attestation + .tdx_quote() + .and_then(|q| Some(q.quote.clone())) + .context("Failed to get TDX quote bytes")?; + let quote_hex = hex::encode("e_bytes); + + // Get collateral and tcb_info + let pccs_url = cfg.pccs_url.as_deref(); + let verified_report = collateral::get_collateral_and_verify("e_bytes, pccs_url) + .await + .context("Failed to get collateral and verify quote")?; + + let collateral_json = serde_json::to_string(&verified_report.collateral) + .context("Failed to serialize collateral")?; + + // Get TCB info from verified report + let td_report = verified_report + .report + .as_td10() + .context("Failed to get TD10 report")?; + + let tcb_info_json = serde_json::to_string(&json!({ + "mrtd": hex::encode(td_report.mr_td), + "rtmr0": hex::encode(td_report.rt_mr0), + "rtmr1": hex::encode(td_report.rt_mr1), + "rtmr2": hex::encode(td_report.rt_mr2), + "rtmr3": hex::encode(td_report.rt_mr3), + })) + .context("Failed to serialize TCB info")?; + + (quote_hex, collateral_json, tcb_info_json) + } else { + anyhow::bail!("Quote must be enabled for NEAR MPC key derivation (attestation required)"); + }; + + // Load or generate NEAR signer (implicit account) + let signer = load_or_generate_near_signer(cfg)?; + let signer = match signer { + Some(s) => s, + None => { + tracing::warn!("Failed to load or generate NEAR signer, cannot call KMS contract"); + return Ok(None); + } + }; + + let kms_client = NearKmsClient::new(&rpc_url, kms_contract_id.clone(), Some(signer))?; + + // Request root key from KMS contract (which will verify attestation and call MPC) + tracing::info!("Calling KMS contract's request_kms_root_key() with attestation..."); + let mpc_response = kms_client + .request_kms_root_key( + "e_hex, + &collateral_json, + &tcb_info_json, + &worker_public_key, + ) + .await + .context("Failed to request root key from KMS contract")?; + + tracing::info!("Received MPC response (big_y, big_c)"); + + // Derive root key from MPC response + let root_key = derive_root_key_from_mpc( + &mpc_response, + ephemeral_private_key, + &mpc_config, + kms_contract_id, + ) + .context("Failed to derive root key from MPC response")?; + + tracing::info!("✅ Successfully derived 32-byte root key from MPC"); + + // Convert root key to Keys structure + let keys = Keys::from_mpc_root_key(root_key, domain, quote_enabled) + .await + .context("Failed to create keys from MPC root key")?; + + Ok(Some(keys)) +} + fn dstack_client() -> DstackGuestClient { let address = dstack_types::dstack_agent_address(); let http_client = PrpcClient::new(address);