diff --git a/.gitignore b/.gitignore index 9f97022..3fa4465 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -target/ \ No newline at end of file +target/ +scripts/__pycache__/ +.DS_Store \ No newline at end of file diff --git a/protobufs/evm.proto b/protobufs/evm.proto new file mode 100644 index 0000000..18d22f8 --- /dev/null +++ b/protobufs/evm.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package arbiter.evm; + +enum EvmError { + EVM_ERROR_UNSPECIFIED = 0; + EVM_ERROR_VAULT_SEALED = 1; + EVM_ERROR_INTERNAL = 2; +} + +message WalletEntry { + bytes address = 1; // 20-byte Ethereum address +} + +message WalletList { + repeated WalletEntry wallets = 1; +} + +message WalletCreateResponse { + oneof result { + WalletEntry wallet = 1; + EvmError error = 2; + } +} + +message WalletListResponse { + oneof result { + WalletList wallets = 1; + EvmError error = 2; + } +} diff --git a/protobufs/user_agent.proto b/protobufs/user_agent.proto index 9120776..b468e31 100644 --- a/protobufs/user_agent.proto +++ b/protobufs/user_agent.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package arbiter.user_agent; import "google/protobuf/empty.proto"; +import "evm.proto"; message AuthChallengeRequest { bytes pubkey = 1; @@ -55,6 +56,8 @@ message UserAgentRequest { UnsealStart unseal_start = 3; UnsealEncryptedKey unseal_encrypted_key = 4; google.protobuf.Empty query_vault_state = 5; + google.protobuf.Empty evm_wallet_create = 6; + google.protobuf.Empty evm_wallet_list = 7; } } message UserAgentResponse { @@ -64,5 +67,7 @@ message UserAgentResponse { UnsealStartResponse unseal_start_response = 3; UnsealResult unseal_result = 4; VaultState vault_state = 5; + arbiter.evm.WalletCreateResponse evm_wallet_create = 6; + arbiter.evm.WalletListResponse evm_wallet_list = 7; } } diff --git a/server/Cargo.lock b/server/Cargo.lock index 8f72a04..97c6f44 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -36,6 +36,630 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4973038846323e4e69a433916522195dce2947770076c03078fc21c80ea0f1c4" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-trie", +] + +[[package]] +name = "alloy-chains" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757" +dependencies = [ + "alloy-primitives", + "num_enum", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c0dc44157867da82c469c13186015b86abef209bf0e41625e4b68bac61d728" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4cdb42df3871cd6b346d6a938ec2ba69a9a0f49d1f82714bc5c48349268434" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca63b7125a981415898ffe2a2a696c83696c9c6bdb1671c8a912946bbd8e49e7" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-core" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e8604b0c092fabc80d075ede181c9b9e596249c70b99253082d7e689836529" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "itoa", + "serde", + "serde_json", + "winnow", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", + "thiserror", +] + +[[package]] +name = "alloy-eip7928" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eips" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f7ef09f21bd1e9cb8a686f168cb4a206646804567f0889eadb8dcc4c9288c8" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "serde", + "serde_with", + "sha2 0.10.9", + "thiserror", +] + +[[package]] +name = "alloy-genesis" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9cf3b99f46615fbf7dc1add0c96553abb7bf88fc9ec70dfbe7ad0b47ba7fe8" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-json-abi" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff42cd777eea61f370c0b10f2648a1c81e0b783066cd7269228aa993afd487f7" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "http", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cbca04f9b410fdc51aaaf88433cbac761213905a65fe832058bcf6690585762" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d6d15e069a8b11f56bef2eccbad2a873c6dd4d4c81d04dda29710f5ea52f04" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "indexmap 2.13.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash", + "serde", + "sha3", +] + +[[package]] +name = "alloy-provider" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d181c8cc7cf4805d7e589bf4074d56d55064fa1a979f005a45a62b047616d870" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-signer", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "either", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-rpc-client" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2792758a93ae32a32e9047c843d536e1448044f78422d71bf7d7c05149e103f" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "alloy-transport-http", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bdcbf9dfd5eea8bfeb078b1d906da8cd3a39c4d4dbe7a628025648e323611f6" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd720b63f82b457610f2eaaf1f32edf44efffe03ae25d537632e7d23e7929e1a" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2dc411f13092f237d2bf6918caf80977fc2f51485f9b90cb2a2f956912c8c9" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror", +] + +[[package]] +name = "alloy-serde" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ce1e0dbf7720eee747700e300c99aac01b1a95bb93f493a01e78ee28bb1a37" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2425c6f314522c78e8198979c8cbf6769362be4da381d4152ea8eefce383535d" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror", +] + +[[package]] +name = "alloy-signer-local" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ecb71ee53d8d9c3fa7bac17542c8116ebc7a9726c91b1bf333ec3d04f5a789" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.13.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa186e560d523d196580c48bf00f1bf62e63041f28ecf276acc22f8b27bb9f53" +dependencies = [ + "alloy-json-rpc", + "auto_impl", + "base64", + "derive_more", + "futures", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "itertools 0.14.0", + "reqwest", + "serde_json", + "tower", + "tracing", + "url", +] + +[[package]] +name = "alloy-trie" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more", + "nybbles", + "serde", + "smallvec", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa0c53e8c1e1ef4d01066b01c737fb62fc9397ab52c6e7bb5669f97d281b9bc" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -67,7 +691,7 @@ dependencies = [ "miette", "prost", "prost-types", - "rand", + "rand 0.10.0", "rcgen", "rstest", "rustls-pki-types", @@ -84,6 +708,7 @@ dependencies = [ name = "arbiter-server" version = "0.1.0" dependencies = [ + "alloy", "arbiter-proto", "argon2", "async-trait", @@ -96,11 +721,12 @@ dependencies = [ "ed25519-dalek", "futures", "insta", + "k256", "kameo", "memsafe", "miette", "pem", - "rand", + "rand 0.10.0", "rcgen", "restructed", "rustls", @@ -150,6 +776,201 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "asn1-rs" version = "0.7.1" @@ -189,6 +1010,28 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -206,6 +1049,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -302,6 +1156,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -326,12 +1186,55 @@ dependencies = [ "tokio", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" @@ -359,17 +1262,82 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f582957c24870b7bfd12bf562c40b4734b533cafbaf8ded31d6d85f462c01" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] [[package]] name = "cc" @@ -389,6 +1357,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha20" version = "0.9.1" @@ -470,6 +1444,53 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-hex" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -494,6 +1515,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.5.0" @@ -509,6 +1545,24 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -539,7 +1593,7 @@ dependencies = [ "cpufeatures 0.2.17", "curve25519-dalek-derive", "fiat-crypto 0.2.9", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -555,7 +1609,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.11.1", "fiat-crypto 0.3.0", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -591,6 +1645,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", "syn 2.0.117", ] @@ -626,6 +1681,16 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -647,6 +1712,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", ] [[package]] @@ -713,6 +1813,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -720,6 +1829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common 0.1.7", "subtle", ] @@ -777,13 +1887,28 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature 2.2.0", + "spki", +] + [[package]] name = "ed25519" version = "3.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" dependencies = [ - "signature", + "signature 3.0.0-rc.10", ] [[package]] @@ -795,16 +1920,51 @@ dependencies = [ "curve25519-dalek 5.0.0-pre.6", "ed25519", "rand_core 0.10.0", - "sha2", + "sha2 0.11.0-rc.5", "subtle", "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] [[package]] name = "encode_unicode" @@ -812,6 +1972,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -834,6 +2014,38 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -852,6 +2064,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.5.7" @@ -901,6 +2125,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.32" @@ -995,6 +2225,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "generic-array" version = "0.14.7" @@ -1003,6 +2239,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1012,8 +2249,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1023,9 +2262,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1054,6 +2295,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.13" @@ -1066,13 +2318,19 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1094,7 +2352,11 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] @@ -1103,12 +2365,36 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "http" version = "1.4.0" @@ -1186,6 +2472,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + [[package]] name = "hyper-timeout" version = "0.5.2" @@ -1205,13 +2508,16 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1357,6 +2663,37 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -1390,12 +2727,46 @@ dependencies = [ "tempfile", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is_ci" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -1431,6 +2802,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2 0.10.9", + "signature 2.2.0", +] + [[package]] name = "kameo" version = "0.19.2" @@ -1458,6 +2844,25 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak-asm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b646a74e746cd25045aa0fd42f4f7f78aa6d119380182c7e63a5593c4ab8df6f" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1476,6 +2881,12 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libsqlite3-sys" version = "0.35.0" @@ -1513,6 +2924,32 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "matchers" version = "0.2.0" @@ -1686,6 +3123,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nybbles" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", ] [[package]] @@ -1724,6 +3207,34 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1758,6 +3269,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem" version = "3.0.6" @@ -1774,6 +3291,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + [[package]] name = "petgraph" version = "0.8.3" @@ -1782,7 +3309,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", ] [[package]] @@ -1817,6 +3344,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1855,6 +3392,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -1865,6 +3411,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -1898,6 +3455,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1907,6 +3486,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.14.3" @@ -1924,7 +3522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", - "itertools", + "itertools 0.14.0", "log", "multimap", "petgraph", @@ -1945,7 +3543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -1981,6 +3579,67 @@ dependencies = [ "pulldown-cmark", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.45" @@ -2002,6 +3661,35 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + [[package]] name = "rand" version = "0.10.0" @@ -2013,6 +3701,26 @@ dependencies = [ "rand_core 0.10.0", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -2022,12 +3730,40 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + [[package]] name = "rand_core" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +dependencies = [ + "rustversion", +] + [[package]] name = "rcgen" version = "0.14.7" @@ -2052,6 +3788,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "regex" version = "1.12.3" @@ -2087,6 +3843,44 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + [[package]] name = "restructed" version = "0.2.2" @@ -2099,6 +3893,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2113,6 +3917,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rsqlite-vfs" version = "0.1.0" @@ -2147,24 +3961,79 @@ dependencies = [ "quote", "regex", "relative-path", - "rustc_version", + "rustc_version 0.4.1", "syn 2.0.117", "unicode-ident", ] +[[package]] +name = "ruint" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-demangle" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.27", ] [[package]] @@ -2198,6 +4067,7 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -2210,6 +4080,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] @@ -2231,6 +4102,48 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scoped-futures" version = "0.1.4" @@ -2246,6 +4159,42 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.10.3" @@ -2255,12 +4204,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.228" @@ -2313,6 +4280,70 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.11.0-rc.5" @@ -2324,6 +4355,26 @@ dependencies = [ "digest 0.11.1", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b31139435f327c93c6038ed350ae4588e2c70a13d50599509fee6349967ba35a" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2349,6 +4400,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "signature" version = "3.0.0-rc.10" @@ -2378,6 +4439,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "smlang" @@ -2410,6 +4474,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlite-wasm-rs" version = "0.5.2" @@ -2428,6 +4502,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_morph" version = "0.1.0" @@ -2510,11 +4590,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -2527,6 +4622,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.27.0" @@ -2610,6 +4711,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.47" @@ -2651,6 +4761,21 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.50.0" @@ -2752,7 +4877,7 @@ version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "winnow", @@ -2846,7 +4971,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 2.13.0", "pin-project-lite", "slab", "sync_wrapper", @@ -2857,6 +4982,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -2942,6 +5085,30 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.9.0" @@ -2960,6 +5127,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" @@ -3010,6 +5183,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -3046,6 +5220,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "want" version = "0.3.1" @@ -3092,6 +5275,20 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.114" @@ -3141,7 +5338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder", "wasmparser", ] @@ -3154,8 +5351,51 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", - "indexmap", - "semver", + "indexmap 2.13.0", + "semver 1.0.27", +] + +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", ] [[package]] @@ -3441,7 +5681,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -3472,7 +5712,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -3491,9 +5731,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", - "semver", + "semver 1.0.27", "serde", "serde_derive", "serde_json", @@ -3507,6 +5747,15 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -3569,6 +5818,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/server/Cargo.toml b/server/Cargo.toml index 6d6e133..618d2de 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -9,7 +9,12 @@ resolver = "3" [workspace.dependencies] -tonic = { version = "0.14.3", features = ["deflate", "gzip", "tls-connect-info", "zstd"] } +tonic = { version = "0.14.3", features = [ + "deflate", + "gzip", + "tls-connect-info", + "zstd", +] } tracing = "0.1.44" tokio = { version = "1.49.0", features = ["full"] } ed25519-dalek = { version = "3.0.0-pre.6", features = ["rand_core"] } @@ -27,6 +32,7 @@ prost-types = { version = "0.14.3", features = ["chrono"] } x25519-dalek = { version = "2.0.1", features = ["getrandom"] } rstest = "0.26.1" rustls-pki-types = "1.14.0" +alloy = "1.7.3" rcgen = { version = "0.14.7", features = [ "aws_lc_rs", "pem", diff --git a/server/crates/arbiter-proto/build.rs b/server/crates/arbiter-proto/build.rs index 4c8a31b..18de523 100644 --- a/server/crates/arbiter-proto/build.rs +++ b/server/crates/arbiter-proto/build.rs @@ -13,6 +13,7 @@ fn main() -> Result<(), Box> { format!("{}/arbiter.proto", PROTOBUF_DIR), format!("{}/user_agent.proto", PROTOBUF_DIR), format!("{}/client.proto", PROTOBUF_DIR), + format!("{}/evm.proto", PROTOBUF_DIR), ], &[PROTOBUF_DIR.to_string()], ) diff --git a/server/crates/arbiter-proto/src/lib.rs b/server/crates/arbiter-proto/src/lib.rs index dae03c0..cf90576 100644 --- a/server/crates/arbiter-proto/src/lib.rs +++ b/server/crates/arbiter-proto/src/lib.rs @@ -13,6 +13,10 @@ pub mod proto { pub mod client { tonic::include_proto!("arbiter.client"); } + + pub mod evm { + tonic::include_proto!("arbiter.evm"); + } } pub static BOOTSTRAP_PATH: &str = "bootstrap_token"; diff --git a/server/crates/arbiter-server/Cargo.toml b/server/crates/arbiter-server/Cargo.toml index 4830b67..8fe03f1 100644 --- a/server/crates/arbiter-server/Cargo.toml +++ b/server/crates/arbiter-server/Cargo.toml @@ -42,6 +42,8 @@ argon2 = { version = "0.5.3", features = ["zeroize"] } restructed = "0.2.2" strum = { version = "0.27.2", features = ["derive"] } pem = "3.0.6" +k256 = "0.13.4" +alloy.workspace = true [dev-dependencies] insta = "1.46.3" diff --git a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql index b2b497e..e5e8d34 100644 --- a/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql +++ b/server/crates/arbiter-server/migrations/2026-02-14-171124-0000_init/up.sql @@ -56,4 +56,171 @@ create table if not exists program_client ( public_key blob not null, created_at integer not null default(unixepoch ('now')), updated_at integer not null default(unixepoch ('now')) -) STRICT; \ No newline at end of file +) STRICT; + +create table if not exists evm_wallet ( + id integer not null primary key, + address blob not null, -- 20-byte Ethereum address + aead_encrypted_id integer not null references aead_encrypted (id) on delete RESTRICT, + created_at integer not null default(unixepoch ('now')) +) STRICT; + +create unique index if not exists uniq_evm_wallet_address on evm_wallet (address); +create unique index if not exists uniq_evm_wallet_aead on evm_wallet (aead_encrypted_id); + +-- Shared grant properties: client scope, timeframe, fee caps, and rate limit +create table if not exists evm_basic_grant ( + id integer not null primary key, + wallet_id integer not null references evm_wallet(id) on delete restrict, + client_id integer not null references program_client(id) on delete restrict, + chain_id integer not null, -- EIP-155 chain ID + valid_from integer, -- unix timestamp (seconds), null = no lower bound + valid_until integer, -- unix timestamp (seconds), null = no upper bound + max_gas_fee_per_gas blob, -- big-endian 32-byte U256, null = unlimited + max_priority_fee_per_gas blob, -- big-endian 32-byte U256, null = unlimited + rate_limit_count integer, -- max transactions in window, null = unlimited + rate_limit_window_secs integer, -- window duration in seconds, null = unlimited + revoked_at integer, -- unix timestamp when revoked, null = still active + created_at integer not null default(unixepoch('now')) +) STRICT; + +create index if not exists idx_evm_basic_grant_wallet_chain on evm_basic_grant(client_id, wallet_id, chain_id); + +-- ERC20 token transfer grant +create table if not exists evm_token_transfer_grant ( + id integer not null primary key, + basic_grant_id integer not null unique references evm_basic_grant(id) on delete cascade, + token_contract blob not null -- 20-byte ERC20 contract address +) STRICT; + +-- Specific recipient addresses for a token transfer grant (only used when target_all = 0) +create table if not exists evm_token_transfer_grant_target ( + id integer not null primary key, + grant_id integer not null references evm_token_transfer_grant(id) on delete cascade, + address blob not null -- 20-byte recipient address +) STRICT; + +create unique index if not exists uniq_token_transfer_target on evm_token_transfer_grant_target(grant_id, address); + +-- Per-window volume limits for token transfer grants +create table if not exists evm_token_transfer_volume_limit ( + id integer not null primary key, + grant_id integer not null references evm_token_transfer_grant(id) on delete cascade, + window_secs integer not null, -- window duration in seconds + max_volume blob not null -- big-endian 32-byte U256 +) STRICT; + +-- ERC20 token approval grant +create table if not exists evm_token_approval_grant ( + id integer not null primary key, + basic_grant_id integer not null unique references evm_basic_grant(id) on delete cascade, + token_contract blob not null, -- 20-byte ERC20 contract address + max_total_approval blob not null -- big-endian 32-byte U256; max cumulative approval value +) STRICT; + +-- Specific spender addresses for a token approval grant (only used when target_all = 0) +create table if not exists evm_token_approval_grant_target ( + id integer not null primary key, + grant_id integer not null references evm_token_approval_grant(id) on delete cascade, + address blob not null -- 20-byte spender address +) STRICT; + +create unique index if not exists uniq_token_approval_target on evm_token_approval_grant_target(grant_id, address); + +-- Plain ether transfer grant +create table if not exists evm_ether_transfer_grant ( + id integer not null primary key, + basic_grant_id integer not null unique references evm_basic_grant(id) on delete cascade +) STRICT; + +-- Specific recipient addresses for an ether transfer grant (only used when target_all = 0) +create table if not exists evm_ether_transfer_grant_target ( + id integer not null primary key, + grant_id integer not null references evm_ether_transfer_grant(id) on delete cascade, + address blob not null -- 20-byte recipient address +) STRICT; + +create unique index if not exists uniq_ether_transfer_target on evm_ether_transfer_grant_target(grant_id, address); + +-- Per-window volume limits for ether transfer grants +create table if not exists evm_ether_transfer_volume_limit ( + id integer not null primary key, + grant_id integer not null references evm_ether_transfer_grant(id) on delete cascade, + window_secs integer not null, + max_volume blob not null -- big-endian 32-byte U256 +) STRICT; + +-- Unknown / opaque contract call grant +create table if not exists evm_unknown_call_grant ( + id integer not null primary key, + basic_grant_id integer not null unique references evm_basic_grant(id) on delete cascade, + contract blob not null, -- 20-byte target contract address + selector blob -- 4-byte function selector, null = allow any selector +) STRICT; + +-- Log table for ether transfer grant usage +create table if not exists evm_ether_transfer_log ( + id integer not null primary key, + grant_id integer not null references evm_ether_transfer_grant(id) on delete restrict, + client_id integer not null references program_client(id) on delete restrict, + wallet_id integer not null references evm_wallet(id) on delete restrict, + chain_id integer not null, -- EIP-155 chain ID + recipient_address blob not null, -- 20-byte recipient address + value blob not null, -- big-endian 32-byte U256 + created_at integer not null default(unixepoch('now')) +) STRICT; + +create index if not exists idx_ether_transfer_log_grant on evm_ether_transfer_log(grant_id); +create index if not exists idx_ether_transfer_log_client on evm_ether_transfer_log(client_id); +create index if not exists idx_ether_transfer_log_wallet on evm_ether_transfer_log(wallet_id); + +-- Log table for token transfer grant usage +create table if not exists evm_token_transfer_log ( + id integer not null primary key, + grant_id integer not null references evm_token_transfer_grant(id) on delete restrict, + client_id integer not null references program_client(id) on delete restrict, + wallet_id integer not null references evm_wallet(id) on delete restrict, + chain_id integer not null, -- EIP-155 chain ID + token_contract blob not null, -- 20-byte ERC20 contract address + recipient_address blob not null, -- 20-byte recipient address + value blob not null, -- big-endian 32-byte U256 + created_at integer not null default(unixepoch('now')) +) STRICT; + +create index if not exists idx_token_transfer_log_grant on evm_token_transfer_log(grant_id); +create index if not exists idx_token_transfer_log_client on evm_token_transfer_log(client_id); +create index if not exists idx_token_transfer_log_wallet on evm_token_transfer_log(wallet_id); + +-- Log table for token approval grant usage +create table if not exists evm_token_approval_log ( + id integer not null primary key, + grant_id integer not null references evm_token_approval_grant(id) on delete restrict, + client_id integer not null references program_client(id) on delete restrict, + wallet_id integer not null references evm_wallet(id) on delete restrict, + chain_id integer not null, -- EIP-155 chain ID + token_contract blob not null, -- 20-byte ERC20 contract address + spender_address blob not null, -- 20-byte spender address + value blob not null, -- big-endian 32-byte U256 + created_at integer not null default(unixepoch('now')) +) STRICT; + +create index if not exists idx_token_approval_log_grant on evm_token_approval_log(grant_id); +create index if not exists idx_token_approval_log_client on evm_token_approval_log(client_id); +create index if not exists idx_token_approval_log_wallet on evm_token_approval_log(wallet_id); + +-- Log table for unknown contract call grant usage +create table if not exists evm_unknown_call_log ( + id integer not null primary key, + grant_id integer not null references evm_unknown_call_grant(id) on delete restrict, + client_id integer not null references program_client(id) on delete restrict, + wallet_id integer not null references evm_wallet(id) on delete restrict, + chain_id integer not null, -- EIP-155 chain ID + contract blob not null, -- 20-byte target contract address + selector blob, -- 4-byte function selector, null if none + call_data blob, -- full call data, null if not stored + created_at integer not null default(unixepoch('now')) +) STRICT; + +create index if not exists idx_unknown_call_log_grant on evm_unknown_call_log(grant_id); +create index if not exists idx_unknown_call_log_client on evm_unknown_call_log(client_id); +create index if not exists idx_unknown_call_log_wallet on evm_unknown_call_log(wallet_id); diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs new file mode 100644 index 0000000..709e3f4 --- /dev/null +++ b/server/crates/arbiter-server/src/actors/evm/mod.rs @@ -0,0 +1,93 @@ +use alloy::primitives::Address; +use diesel::{QueryDsl, SelectableHelper as _, dsl::insert_into}; +use diesel_async::RunQueryDsl; +use kameo::{Actor, actor::ActorRef, messages}; +use memsafe::MemSafe; +use rand::{SeedableRng, rng, rngs::StdRng}; + +use crate::{ + actors::keyholder::{CreateNew, KeyHolder}, + db::{self, DatabasePool, models, schema}, +}; + +pub use crate::evm::safe_signer; + +#[derive(Debug, thiserror::Error, miette::Diagnostic)] +pub enum Error { + #[error("Keyholder error: {0}")] + #[diagnostic(code(arbiter::evm::keyholder))] + Keyholder(#[from] crate::actors::keyholder::Error), + + #[error("Keyholder mailbox error")] + #[diagnostic(code(arbiter::evm::keyholder_send))] + KeyholderSend, + + #[error("Database error: {0}")] + #[diagnostic(code(arbiter::evm::database))] + Database(#[from] diesel::result::Error), + + #[error("Database pool error: {0}")] + #[diagnostic(code(arbiter::evm::database_pool))] + DatabasePool(#[from] db::PoolError), +} + +#[derive(Actor)] +pub struct EvmActor { + pub keyholder: ActorRef, + pub db: DatabasePool, + pub rng: StdRng, +} + +impl EvmActor { + pub fn new(keyholder: ActorRef, db: DatabasePool) -> Self { + // is it safe to seed rng from system once? + // todo: audit + let rng = StdRng::from_rng(&mut rng()); + Self { keyholder, db, rng } + } +} + +#[messages] +impl EvmActor { + #[message] + pub async fn generate(&mut self) -> Result { + let (mut key_cell, address) = safe_signer::generate(&mut self.rng); + + // Move raw key bytes into a Vec MemSafe for KeyHolder + let plaintext = { + let reader = key_cell.read().expect("MemSafe read"); + MemSafe::new(reader.to_vec()).expect("MemSafe allocation") + }; + + let aead_id: i32 = self + .keyholder + .ask(CreateNew { plaintext }) + .await + .map_err(|_| Error::KeyholderSend)?; + + let mut conn = self.db.get().await?; + insert_into(schema::evm_wallet::table) + .values(&models::NewEvmWallet { + address: address.as_slice().to_vec(), + aead_encrypted_id: aead_id, + }) + .execute(&mut conn) + .await?; + + Ok(address) + } + + #[message] + pub async fn list_wallets(&self) -> Result, Error> { + let mut conn = self.db.get().await?; + let rows: Vec = schema::evm_wallet::table + .select(models::EvmWallet::as_select()) + .load(&mut conn) + .await?; + + Ok(rows + .into_iter() + .map(|w| Address::from_slice(&w.address)) + .collect()) + } +} diff --git a/server/crates/arbiter-server/src/actors/mod.rs b/server/crates/arbiter-server/src/actors/mod.rs index 33bdb5e..4a678b1 100644 --- a/server/crates/arbiter-server/src/actors/mod.rs +++ b/server/crates/arbiter-server/src/actors/mod.rs @@ -3,15 +3,16 @@ use miette::Diagnostic; use thiserror::Error; use crate::{ - actors::{bootstrap::Bootstrapper, keyholder::KeyHolder, router::MessageRouter}, + actors::{bootstrap::Bootstrapper, evm::EvmActor, keyholder::KeyHolder, router::MessageRouter}, db, }; pub mod bootstrap; -pub mod router; -pub mod keyholder; -pub mod user_agent; pub mod client; +mod evm; +pub mod keyholder; +pub mod router; +pub mod user_agent; #[derive(Error, Debug, Diagnostic)] pub enum SpawnError { @@ -30,13 +31,16 @@ pub struct GlobalActors { pub key_holder: ActorRef, pub bootstrapper: ActorRef, pub router: ActorRef, + pub evm: ActorRef, } impl GlobalActors { pub async fn spawn(db: db::DatabasePool) -> Result { + let key_holder = KeyHolder::spawn(KeyHolder::new(db.clone()).await?); Ok(Self { bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?), - key_holder: KeyHolder::spawn(KeyHolder::new(db.clone()).await?), + evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)), + key_holder, router: MessageRouter::spawn(MessageRouter::default()), }) } diff --git a/server/crates/arbiter-server/src/actors/user_agent/session.rs b/server/crates/arbiter-server/src/actors/user_agent/session.rs index 04d3260..de04f70 100644 --- a/server/crates/arbiter-server/src/actors/user_agent/session.rs +++ b/server/crates/arbiter-server/src/actors/user_agent/session.rs @@ -1,19 +1,26 @@ use std::{ops::DerefMut, sync::Mutex}; -use arbiter_proto::proto::user_agent::{ - UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest, - UserAgentResponse, user_agent_request::Payload as UserAgentRequestPayload, - user_agent_response::Payload as UserAgentResponsePayload, +use arbiter_proto::proto::{ + evm as evm_proto, + user_agent::{ + UnsealEncryptedKey, UnsealResult, UnsealStart, UnsealStartResponse, UserAgentRequest, + UserAgentResponse, user_agent_request::Payload as UserAgentRequestPayload, + user_agent_response::Payload as UserAgentResponsePayload, + }, }; use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit}; use ed25519_dalek::VerifyingKey; -use kameo::{Actor, error::SendError}; +use kameo::{ + Actor, + error::SendError, +}; use memsafe::MemSafe; use tokio::select; use tracing::{error, info}; use x25519_dalek::{EphemeralSecret, PublicKey}; use crate::actors::{ + evm::{Generate, ListWallets}, keyholder::{self, TryUnseal}, router::RegisterUserAgent, user_agent::{UserAgentConnection, UserAgentError}, @@ -58,6 +65,8 @@ impl UserAgentSession { UserAgentRequestPayload::UnsealEncryptedKey(unseal_encrypted_key) => { self.handle_unseal_encrypted_key(unseal_encrypted_key).await } + UserAgentRequestPayload::EvmWalletCreate(_) => self.handle_evm_wallet_create().await, + UserAgentRequestPayload::EvmWalletList(_) => self.handle_evm_wallet_list().await, _ => Err(UserAgentError::UnexpectedRequestPayload), } } @@ -178,6 +187,64 @@ impl UserAgentSession { } } +impl UserAgentSession { + async fn handle_evm_wallet_create(&mut self) -> Output { + use evm_proto::wallet_create_response::Result as CreateResult; + + let result = match self.props.actors.evm.ask(Generate {}).await { + Ok(address) => CreateResult::Wallet(evm_proto::WalletEntry { + address: address.as_slice().to_vec(), + }), + Err(err) => CreateResult::Error(map_evm_error("wallet create", err).into()), + }; + + Ok(response(UserAgentResponsePayload::EvmWalletCreate( + evm_proto::WalletCreateResponse { + result: Some(result), + }, + ))) + } + + async fn handle_evm_wallet_list(&mut self) -> Output { + use evm_proto::wallet_list_response::Result as ListResult; + + let result = match self.props.actors.evm.ask(ListWallets {}).await { + Ok(wallets) => ListResult::Wallets(evm_proto::WalletList { + wallets: wallets + .into_iter() + .map(|addr| evm_proto::WalletEntry { + address: addr.as_slice().to_vec(), + }) + .collect(), + }), + Err(err) => ListResult::Error(map_evm_error("wallet list", err).into()), + }; + + Ok(response(UserAgentResponsePayload::EvmWalletList( + evm_proto::WalletListResponse { + result: Some(result), + }, + ))) + } +} + +fn map_evm_error(op: &str, err: SendError) -> evm_proto::EvmError { + use crate::actors::{evm::Error as EvmError, keyholder::Error as KhError}; + match err { + SendError::HandlerError(EvmError::Keyholder(KhError::NotBootstrapped)) => { + evm_proto::EvmError::VaultSealed + } + SendError::HandlerError(err) => { + error!(?err, "EVM {op} failed"); + evm_proto::EvmError::Internal + } + _ => { + error!("EVM actor unreachable during {op}"); + evm_proto::EvmError::Internal + } + } +} + impl Actor for UserAgentSession { type Args = Self; diff --git a/server/crates/arbiter-server/src/db/models.rs b/server/crates/arbiter-server/src/db/models.rs index 453e071..97f61ab 100644 --- a/server/crates/arbiter-server/src/db/models.rs +++ b/server/crates/arbiter-server/src/db/models.rs @@ -1,14 +1,67 @@ #![allow(unused)] #![allow(clippy::all)] -use crate::db::schema::{self, aead_encrypted, arbiter_settings, root_key_history, tls_history}; +use crate::db::schema::{ + self, aead_encrypted, arbiter_settings, evm_basic_grant, evm_ether_transfer_grant, + evm_ether_transfer_grant_target, evm_ether_transfer_log, evm_ether_transfer_volume_limit, + evm_token_approval_grant, evm_token_approval_grant_target, + evm_token_approval_log, evm_token_transfer_grant, evm_token_transfer_grant_target, + evm_token_transfer_log, evm_token_transfer_volume_limit, evm_unknown_call_grant, + evm_unknown_call_log, evm_wallet, root_key_history, tls_history, +}; +use chrono::{DateTime, Utc}; use diesel::{prelude::*, sqlite::Sqlite}; use restructed::Models; pub mod types { + use std::os::unix; + use chrono::{DateTime, Utc}; - pub struct SqliteTimestamp(DateTime); + use diesel::{ + deserialize::{FromSql, FromSqlRow}, + expression::AsExpression, + serialize::{IsNull, ToSql}, + sql_types::Integer, + sqlite::{Sqlite, SqliteType}, + }; + + #[derive(Debug, FromSqlRow, AsExpression)] + #[sql_type = "Integer"] + #[repr(transparent)] // hint compiler to optimize the wrapper struct away + pub struct SqliteTimestamp(pub DateTime); + + impl ToSql for SqliteTimestamp { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, Sqlite>, + ) -> diesel::serialize::Result { + let unix_timestamp = self.0.timestamp() as i32; + out.set_value(unix_timestamp); + Ok(IsNull::No) + } + } + + impl FromSql for SqliteTimestamp { + fn from_sql( + mut bytes: ::RawValue<'_>, + ) -> diesel::deserialize::Result { + let Some(SqliteType::Integer) = bytes.value_type() else { + return Err(format!( + "Expected Integer type for SqliteTimestamp, got {:?}", + bytes.value_type() + ) + .into()); + }; + + let unix_timestamp = bytes.read_integer(); + let datetime = DateTime::from_timestamp(unix_timestamp as i64, 0) + .ok_or("Timestamp is out of bounds")?; + + Ok(SqliteTimestamp(datetime)) + } + } } +pub use types::*; #[derive(Models, Queryable, Debug, Insertable, Selectable)] #[view( @@ -58,9 +111,9 @@ pub struct TlsHistory { pub id: i32, pub cert: String, pub cert_key: String, // PEM Encoded private key - pub ca_cert: String, // PEM Encoded certificate for cert signing - pub ca_key: String, // PEM Encoded public key for cert signing - pub created_at: i32, + pub ca_cert: String, // PEM Encoded certificate for cert signing + pub ca_key: String, // PEM Encoded public key for cert signing + pub created_at: SqliteTimestamp, } #[derive(Queryable, Debug, Insertable, Selectable)] @@ -68,7 +121,22 @@ pub struct TlsHistory { pub struct ArbiterSettings { pub id: i32, pub root_key_id: Option, // references root_key_history.id - pub tls_id: Option, // references tls_history.id + pub tls_id: Option, // references tls_history.id +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_wallet, check_for_backend(Sqlite))] +#[view( + NewEvmWallet, + derive(Insertable), + omit(id, created_at), + attributes_with = "deriveless" +)] +pub struct EvmWallet { + pub id: i32, + pub address: Vec, + pub aead_encrypted_id: i32, + pub created_at: SqliteTimestamp, } #[derive(Queryable, Debug)] @@ -77,8 +145,8 @@ pub struct ProgramClient { pub id: i32, pub public_key: Vec, pub nonce: i32, - pub created_at: i32, - pub updated_at: i32, + pub created_at: SqliteTimestamp, + pub updated_at: SqliteTimestamp, } #[derive(Queryable, Debug)] @@ -87,6 +155,237 @@ pub struct UseragentClient { pub id: i32, pub public_key: Vec, pub nonce: i32, - pub created_at: i32, - pub updated_at: i32, + pub created_at: SqliteTimestamp, + pub updated_at: SqliteTimestamp, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_basic_grant, check_for_backend(Sqlite))] +#[view( + NewEvmBasicGrant, + derive(Insertable), + omit(id, created_at), + attributes_with = "deriveless" +)] +pub struct EvmBasicGrant { + pub id: i32, + pub wallet_id: i32, // references evm_wallet.id + pub client_id: i32, // references program_client.id + pub chain_id: i32, + pub valid_from: Option, + pub valid_until: Option, + pub max_gas_fee_per_gas: Option>, + pub max_priority_fee_per_gas: Option>, + pub rate_limit_count: Option, + pub rate_limit_window_secs: Option, + pub revoked_at: Option, + pub created_at: SqliteTimestamp, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_ether_transfer_grant, check_for_backend(Sqlite))] +#[view( + NewEvmEtherTransferGrant, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmEtherTransferGrant { + pub id: i32, + pub basic_grant_id: i32, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_ether_transfer_grant_target, check_for_backend(Sqlite))] +#[view( + NewEvmEtherTransferGrantTarget, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmEtherTransferGrantTarget { + pub id: i32, + pub grant_id: i32, + pub address: Vec, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_ether_transfer_volume_limit, check_for_backend(Sqlite))] +#[view( + NewEvmEtherTransferVolumeLimit, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmEtherTransferVolumeLimit { + pub id: i32, + pub grant_id: i32, + pub window_secs: i32, + pub max_volume: Vec, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_token_approval_grant, check_for_backend(Sqlite))] +#[view( + NewEvmTokenApprovalGrant, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmTokenApprovalGrant { + pub id: i32, + pub basic_grant_id: i32, + pub token_contract: Vec, + pub max_total_approval: Vec, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_token_approval_grant_target, check_for_backend(Sqlite))] +#[view( + NewEvmTokenApprovalGrantTarget, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmTokenApprovalGrantTarget { + pub id: i32, + pub grant_id: i32, + pub address: Vec, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_token_transfer_grant, check_for_backend(Sqlite))] +#[view( + NewEvmTokenTransferGrant, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmTokenTransferGrant { + pub id: i32, + pub basic_grant_id: i32, + pub token_contract: Vec, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_token_transfer_grant_target, check_for_backend(Sqlite))] +#[view( + NewEvmTokenTransferGrantTarget, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmTokenTransferGrantTarget { + pub id: i32, + pub grant_id: i32, + pub address: Vec, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_token_transfer_volume_limit, check_for_backend(Sqlite))] +#[view( + NewEvmTokenTransferVolumeLimit, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmTokenTransferVolumeLimit { + pub id: i32, + pub grant_id: i32, + pub window_secs: i32, + pub max_volume: Vec, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_unknown_call_grant, check_for_backend(Sqlite))] +#[view( + NewEvmUnknownCallGrant, + derive(Insertable), + omit(id), + attributes_with = "deriveless" +)] +pub struct EvmUnknownCallGrant { + pub id: i32, + pub basic_grant_id: i32, + pub contract: Vec, + pub selector: Option>, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_ether_transfer_log, check_for_backend(Sqlite))] +#[view( + NewEvmEtherTransferLog, + derive(Insertable), + omit(id, created_at), + attributes_with = "deriveless" +)] +pub struct EvmEtherTransferLog { + pub id: i32, + pub grant_id: i32, + pub client_id: i32, + pub wallet_id: i32, + pub chain_id: i32, + pub recipient_address: Vec, + pub value: Vec, + pub created_at: SqliteTimestamp, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_token_transfer_log, check_for_backend(Sqlite))] +#[view( + NewEvmTokenTransferLog, + derive(Insertable), + omit(id, created_at), + attributes_with = "deriveless" +)] +pub struct EvmTokenTransferLog { + pub id: i32, + pub grant_id: i32, + pub client_id: i32, + pub wallet_id: i32, + pub chain_id: i32, + pub token_contract: Vec, + pub recipient_address: Vec, + pub value: Vec, + pub created_at: SqliteTimestamp, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_token_approval_log, check_for_backend(Sqlite))] +#[view( + NewEvmTokenApprovalLog, + derive(Insertable), + omit(id, created_at), + attributes_with = "deriveless" +)] +pub struct EvmTokenApprovalLog { + pub id: i32, + pub grant_id: i32, + pub client_id: i32, + pub wallet_id: i32, + pub chain_id: i32, + pub token_contract: Vec, + pub spender_address: Vec, + pub value: Vec, + pub created_at: SqliteTimestamp, +} + +#[derive(Models, Queryable, Debug, Insertable, Selectable)] +#[diesel(table_name = evm_unknown_call_log, check_for_backend(Sqlite))] +#[view( + NewEvmUnknownCallLog, + derive(Insertable), + omit(id, created_at), + attributes_with = "deriveless" +)] +pub struct EvmUnknownCallLog { + pub id: i32, + pub grant_id: i32, + pub client_id: i32, + pub wallet_id: i32, + pub chain_id: i32, + pub contract: Vec, + pub selector: Option>, + pub call_data: Option>, + pub created_at: SqliteTimestamp, } diff --git a/server/crates/arbiter-server/src/db/schema.rs b/server/crates/arbiter-server/src/db/schema.rs index 28cb4dc..7a035f3 100644 --- a/server/crates/arbiter-server/src/db/schema.rs +++ b/server/crates/arbiter-server/src/db/schema.rs @@ -20,6 +20,162 @@ diesel::table! { } } +diesel::table! { + evm_basic_grant (id) { + id -> Integer, + wallet_id -> Integer, + client_id -> Integer, + chain_id -> Integer, + valid_from -> Nullable, + valid_until -> Nullable, + max_gas_fee_per_gas -> Nullable, + max_priority_fee_per_gas -> Nullable, + rate_limit_count -> Nullable, + rate_limit_window_secs -> Nullable, + revoked_at -> Nullable, + created_at -> Integer, + } +} + +diesel::table! { + evm_ether_transfer_grant (id) { + id -> Integer, + basic_grant_id -> Integer, + } +} + +diesel::table! { + evm_ether_transfer_grant_target (id) { + id -> Integer, + grant_id -> Integer, + address -> Binary, + } +} + +diesel::table! { + evm_ether_transfer_log (id) { + id -> Integer, + grant_id -> Integer, + client_id -> Integer, + wallet_id -> Integer, + chain_id -> Integer, + recipient_address -> Binary, + value -> Binary, + created_at -> Integer, + } +} + +diesel::table! { + evm_ether_transfer_volume_limit (id) { + id -> Integer, + grant_id -> Integer, + window_secs -> Integer, + max_volume -> Binary, + } +} + +diesel::table! { + evm_token_approval_grant (id) { + id -> Integer, + basic_grant_id -> Integer, + token_contract -> Binary, + max_total_approval -> Binary, + } +} + +diesel::table! { + evm_token_approval_grant_target (id) { + id -> Integer, + grant_id -> Integer, + address -> Binary, + } +} + +diesel::table! { + evm_token_approval_log (id) { + id -> Integer, + grant_id -> Integer, + client_id -> Integer, + wallet_id -> Integer, + chain_id -> Integer, + token_contract -> Binary, + spender_address -> Binary, + value -> Binary, + created_at -> Integer, + } +} + +diesel::table! { + evm_token_transfer_grant (id) { + id -> Integer, + basic_grant_id -> Integer, + token_contract -> Binary, + } +} + +diesel::table! { + evm_token_transfer_grant_target (id) { + id -> Integer, + grant_id -> Integer, + address -> Binary, + } +} + +diesel::table! { + evm_token_transfer_log (id) { + id -> Integer, + grant_id -> Integer, + client_id -> Integer, + wallet_id -> Integer, + chain_id -> Integer, + token_contract -> Binary, + recipient_address -> Binary, + value -> Binary, + created_at -> Integer, + } +} + +diesel::table! { + evm_token_transfer_volume_limit (id) { + id -> Integer, + grant_id -> Integer, + window_secs -> Integer, + max_volume -> Binary, + } +} + +diesel::table! { + evm_unknown_call_grant (id) { + id -> Integer, + basic_grant_id -> Integer, + contract -> Binary, + selector -> Nullable, + } +} + +diesel::table! { + evm_unknown_call_log (id) { + id -> Integer, + grant_id -> Integer, + client_id -> Integer, + wallet_id -> Integer, + chain_id -> Integer, + contract -> Binary, + selector -> Nullable, + call_data -> Nullable, + created_at -> Integer, + } +} + +diesel::table! { + evm_wallet (id) { + id -> Integer, + address -> Binary, + aead_encrypted_id -> Integer, + created_at -> Integer, + } +} + diesel::table! { program_client (id) { id -> Integer, @@ -66,10 +222,49 @@ diesel::table! { diesel::joinable!(aead_encrypted -> root_key_history (associated_root_key_id)); diesel::joinable!(arbiter_settings -> root_key_history (root_key_id)); diesel::joinable!(arbiter_settings -> tls_history (tls_id)); +diesel::joinable!(evm_basic_grant -> evm_wallet (wallet_id)); +diesel::joinable!(evm_basic_grant -> program_client (client_id)); +diesel::joinable!(evm_ether_transfer_grant -> evm_basic_grant (basic_grant_id)); +diesel::joinable!(evm_ether_transfer_grant_target -> evm_ether_transfer_grant (grant_id)); +diesel::joinable!(evm_ether_transfer_log -> evm_ether_transfer_grant (grant_id)); +diesel::joinable!(evm_ether_transfer_log -> evm_wallet (wallet_id)); +diesel::joinable!(evm_ether_transfer_log -> program_client (client_id)); +diesel::joinable!(evm_ether_transfer_volume_limit -> evm_ether_transfer_grant (grant_id)); +diesel::joinable!(evm_token_approval_grant -> evm_basic_grant (basic_grant_id)); +diesel::joinable!(evm_token_approval_grant_target -> evm_token_approval_grant (grant_id)); +diesel::joinable!(evm_token_approval_log -> evm_token_approval_grant (grant_id)); +diesel::joinable!(evm_token_approval_log -> evm_wallet (wallet_id)); +diesel::joinable!(evm_token_approval_log -> program_client (client_id)); +diesel::joinable!(evm_token_transfer_grant -> evm_basic_grant (basic_grant_id)); +diesel::joinable!(evm_token_transfer_grant_target -> evm_token_transfer_grant (grant_id)); +diesel::joinable!(evm_token_transfer_log -> evm_token_transfer_grant (grant_id)); +diesel::joinable!(evm_token_transfer_log -> evm_wallet (wallet_id)); +diesel::joinable!(evm_token_transfer_log -> program_client (client_id)); +diesel::joinable!(evm_token_transfer_volume_limit -> evm_token_transfer_grant (grant_id)); +diesel::joinable!(evm_unknown_call_grant -> evm_basic_grant (basic_grant_id)); +diesel::joinable!(evm_unknown_call_log -> evm_unknown_call_grant (grant_id)); +diesel::joinable!(evm_unknown_call_log -> evm_wallet (wallet_id)); +diesel::joinable!(evm_unknown_call_log -> program_client (client_id)); +diesel::joinable!(evm_wallet -> aead_encrypted (aead_encrypted_id)); diesel::allow_tables_to_appear_in_same_query!( aead_encrypted, arbiter_settings, + evm_basic_grant, + evm_ether_transfer_grant, + evm_ether_transfer_grant_target, + evm_ether_transfer_log, + evm_ether_transfer_volume_limit, + evm_token_approval_grant, + evm_token_approval_grant_target, + evm_token_approval_log, + evm_token_transfer_grant, + evm_token_transfer_grant_target, + evm_token_transfer_log, + evm_token_transfer_volume_limit, + evm_unknown_call_grant, + evm_unknown_call_log, + evm_wallet, program_client, root_key_history, tls_history, diff --git a/server/crates/arbiter-server/src/evm/mod.rs b/server/crates/arbiter-server/src/evm/mod.rs new file mode 100644 index 0000000..053fed5 --- /dev/null +++ b/server/crates/arbiter-server/src/evm/mod.rs @@ -0,0 +1,132 @@ +pub mod safe_signer; + +use alloy::{ + consensus::TxEip1559, + primitives::TxKind, +}; +use diesel::insert_into; +use diesel_async::{AsyncConnection, RunQueryDsl}; + +use crate::{ + db::{ + self, + models::{EvmBasicGrant, NewEvmBasicGrant, SqliteTimestamp}, schema, + }, + evm::policies::{ + EvalContext, FullGrant, Policy, SpecificMeaning, ether_transfer::EtherTransfer + }, +}; + +pub mod policies; +mod utils; + +#[derive(Debug, thiserror::Error, miette::Diagnostic)] +pub enum AnalyzeError { + #[error("Engine doesn't support granting permissions for contract creation")] + #[diagnostic(code(arbiter_server::evm::analyze_error::contract_creation_not_supported))] + ContractCreationNotSupported, + + #[error("Unsupported transaction type")] + #[diagnostic(code(arbiter_server::evm::analyze_error::unsupported_transaction_type))] + UnsupportedTransactionType, +} + +#[derive(Debug, thiserror::Error, miette::Diagnostic)] +pub enum CreationError { + #[error("Database connection pool error")] + #[diagnostic(code(arbiter_server::evm::creation_error::database_error))] + Pool(#[from] db::PoolError), + + #[error("Database returned error")] + #[diagnostic(code(arbiter_server::evm::creation_error::database_error))] + Database(#[from] diesel::result::Error), +} + +// Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed +pub struct Engine { + db: db::DatabasePool, +} + +impl Engine { + pub fn new(db: db::DatabasePool) -> Self { + Self { db } + } + + pub async fn create_grant( + &self, + client_id: i32, + full_grant: FullGrant, + ) -> Result { + let mut conn = self.db.get().await?; + + let id = conn.transaction(|conn| { + Box::pin(async move { + use schema::evm_basic_grant; + + let basic_grant: EvmBasicGrant = insert_into(evm_basic_grant::table) + .values(&NewEvmBasicGrant { + wallet_id: full_grant.basic.wallet_id, + chain_id: full_grant.basic.chain as i32, + client_id: client_id, + valid_from: full_grant.basic.valid_from.map(SqliteTimestamp), + valid_until: full_grant.basic.valid_until.map(SqliteTimestamp), + max_gas_fee_per_gas: full_grant + .basic + .max_gas_fee_per_gas + .map(|fee| utils::u256_to_bytes(fee).to_vec()), + max_priority_fee_per_gas: full_grant + .basic + .max_priority_fee_per_gas + .map(|fee| utils::u256_to_bytes(fee).to_vec()), + rate_limit_count: full_grant + .basic + .rate_limit + .as_ref() + .map(|rl| rl.count as i32), + rate_limit_window_secs: full_grant + .basic + .rate_limit + .as_ref() + .map(|rl| rl.window.num_seconds() as i32), + revoked_at: None, + }) + .returning(evm_basic_grant::all_columns) + .get_result(conn) + .await?; + + P::create_grant(&basic_grant, &full_grant.specific, conn).await + }) + }) + .await?; + + Ok(id) + } + + async fn perform_transaction(&self, _context: EvalContext, _meaning: &P::Meaning) { + } + + pub async fn analyze_transaction( + &self, + wallet_id: i32, + client_id: i32, + transaction: TxEip1559, + ) -> Result { + let TxKind::Call(to) = transaction.to else { + return Err(AnalyzeError::ContractCreationNotSupported); + }; + let context = policies::EvalContext { + wallet_id, + client_id, + chain: transaction.chain_id, + to: to, + value: transaction.value, + calldata: transaction.input.clone(), + }; + + + if let Some(meaning) = EtherTransfer::analyze(&context) { + return Ok(SpecificMeaning::EtherTransfer(meaning)); + } + Err(AnalyzeError::UnsupportedTransactionType) + } +} diff --git a/server/crates/arbiter-server/src/evm/policies.rs b/server/crates/arbiter-server/src/evm/policies.rs new file mode 100644 index 0000000..1234275 --- /dev/null +++ b/server/crates/arbiter-server/src/evm/policies.rs @@ -0,0 +1,129 @@ +use std::fmt::Display; + +use alloy::primitives::{Address, Bytes, ChainId, U256}; +use chrono::{DateTime, Duration, Utc}; +use diesel::{result::QueryResult, sqlite::Sqlite}; +use diesel_async::AsyncConnection; +use miette::Diagnostic; +use thiserror::Error; + +use crate::db::models; + +pub mod ether_transfer; + +pub struct EvalContext { + // Which wallet is this transaction for + pub client_id: i32, + pub wallet_id: i32, + + // The transaction data + pub chain: ChainId, + pub to: Address, + pub value: U256, + pub calldata: Bytes, +} + +#[derive(Debug, Error, Diagnostic)] +pub enum EvalViolation { + #[error("This grant doesn't allow transactions to the target address {target}")] + #[diagnostic(code(arbiter_server::evm::eval_violation::invalid_target))] + InvalidTarget { target: Address }, + + #[error("Gas limit exceeded for this grant")] + #[diagnostic(code(arbiter_server::evm::eval_violation::gas_limit_exceeded))] + GasLimitExceeded { + max_gas_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + }, + + #[error("Rate limit exceeded for this grant")] + #[diagnostic(code(arbiter_server::evm::eval_violation::rate_limit_exceeded))] + RateLimitExceeded, + + #[error("Transaction exceeds volumetric limits of the grant")] + #[diagnostic(code(arbiter_server::evm::eval_violation::volumetric_limit_exceeded))] + VolumetricLimitExceeded, + + #[error("Transaction is outside of the grant's validity period")] + #[diagnostic(code(arbiter_server::evm::eval_violation::invalid_time))] + InvalidTime, +} + +pub type DatabaseID = i32; + +pub struct GrantMetadata { + pub basic_grant_id: DatabaseID, + pub policy_grant_id: DatabaseID, +} + +pub trait Policy: Sized { + type Grant: Send + 'static + Into; + type Meaning: Display + Send + 'static + Into; + + fn analyze(context: &EvalContext) -> Option; + + // Evaluate whether a transaction with the given meaning complies with the provided grant, and return any violations if not + // Empty vector means transaction is compliant with the grant + fn evaluate( + meaning: &Self::Meaning, + grant: &Self::Grant, + meta: &GrantMetadata, + db: &mut impl AsyncConnection, + ) -> impl Future>> + Send; + + // Create a new grant in the database based on the provided grant details, and return its ID + fn create_grant( + basic: &models::EvmBasicGrant, + grant: &Self::Grant, + conn: &mut impl AsyncConnection, + ) -> impl std::future::Future> + Send; + + // Try to find an existing grant that matches the transaction context, and return its details if found + // Additionally, return ID of basic grant for shared-logic checks like rate limits and validity periods + fn try_find_grant( + context: &EvalContext, + conn: &mut impl AsyncConnection, + ) -> impl Future>>; + + // Records, updates or deletes rate limits + // In other words, records grant-specific things after transaction is executed + fn record_transaction( + context: &EvalContext, + grant: &GrantMetadata, + conn: &mut impl AsyncConnection, + ) -> impl Future>; +} + +// Classification of what transaction does +pub enum SpecificMeaning { + EtherTransfer(ether_transfer::Meaning), +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TransactionRateLimit { + pub count: u32, + pub window: Duration, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct BasicGrant { + pub wallet_id: i32, + pub chain: ChainId, + + pub valid_from: Option>, + pub valid_until: Option>, + + pub max_gas_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + + pub rate_limit: Option, +} + +pub enum SpecificGrant { + EtherTransfer(ether_transfer::Grant), +} + +pub struct FullGrant { + pub basic: BasicGrant, + pub specific: PolicyGrant, +} \ No newline at end of file diff --git a/server/crates/arbiter-server/src/evm/policies/ether_transfer.rs b/server/crates/arbiter-server/src/evm/policies/ether_transfer.rs new file mode 100644 index 0000000..55fb59a --- /dev/null +++ b/server/crates/arbiter-server/src/evm/policies/ether_transfer.rs @@ -0,0 +1,310 @@ +use std::{fmt::Display, time::Duration}; + +use alloy::primitives::{Address, U256}; +use chrono::{DateTime, Utc}; +use diesel::dsl::insert_into; +use diesel::sqlite::Sqlite; +use diesel::{ExpressionMethods, JoinOnDsl, prelude::*}; +use diesel_async::{AsyncConnection, RunQueryDsl}; + +use crate::db::models::{ + EvmEtherTransferGrant, EvmEtherTransferGrantTarget, EvmEtherTransferVolumeLimit, SqliteTimestamp, +}; +use crate::evm::policies::{GrantMetadata, SpecificGrant, SpecificMeaning}; +use crate::{ + db::{ + models::{ + self, NewEvmEtherTransferGrant, NewEvmEtherTransferGrantTarget, + NewEvmEtherTransferVolumeLimit, + }, + schema::{ + evm_ether_transfer_grant, evm_ether_transfer_grant_target, + evm_ether_transfer_volume_limit, + }, + }, + evm::{policies::Policy, utils}, +}; + +use super::{DatabaseID, EvalContext, EvalViolation}; + +// Plain ether transfer +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Meaning { + to: Address, + value: U256, +} +impl Display for Meaning { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Ether transfer of {} to {}", + self.value, + self.to.to_string() + ) + } +} +impl Into for Meaning { + fn into(self) -> SpecificMeaning { + SpecificMeaning::EtherTransfer(self) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct VolumeLimit { + window: Duration, + max_volume: U256, +} + +// A grant for ether transfers, which can be scoped to specific target addresses and volume limits +pub struct Grant { + target: Vec
, + limits: Vec, +} + +impl Into for Grant { + fn into(self) -> SpecificGrant { + SpecificGrant::EtherTransfer(self) + } +} + +async fn query_relevant_past_transaction( + grant_id: i32, + longest_window: Duration, + db: &mut impl AsyncConnection, +) -> QueryResult)>> { + use crate::db::schema::evm_ether_transfer_log; + let past_transactions: Vec<(Vec, SqliteTimestamp)> = + evm_ether_transfer_log::table + .filter(evm_ether_transfer_log::grant_id.eq(grant_id)) + .filter( + evm_ether_transfer_log::created_at + .ge(SqliteTimestamp(chrono::Utc::now() - longest_window)), + ) + .select(( + evm_ether_transfer_log::value, + evm_ether_transfer_log::created_at, + )) + .load(db) + .await?; + let past_transaction: Vec<(U256, DateTime)> = past_transactions + .into_iter() + .filter_map(|(value_bytes, timestamp)| { + let value = utils::bytes_to_u256(&value_bytes)?; + Some((value, timestamp.0)) + }) + .collect(); + Ok(past_transaction) +} + +async fn check_rate_limits( + grant: &Grant, + meta: &GrantMetadata, + db: &mut impl AsyncConnection, +) -> QueryResult> { + let mut violations = Vec::new(); + // This has double meaning: checks for limit presence, and finds biggest window + // to extract all needed historical transactions in one go later + let longest_window = grant.limits.iter().map(|limit| limit.window).max(); + + if let Some(longest_window) = longest_window { + let _past_transaction = query_relevant_past_transaction(meta.policy_grant_id, longest_window, db).await?; + + for limit in &grant.limits { + let window_start = chrono::Utc::now() - limit.window; + let cumulative_volume: U256 = _past_transaction + .iter() + .filter(|(_, timestamp)| timestamp >= &window_start) + .fold(U256::default(), |acc, (value, _)| acc + *value); + + if cumulative_volume > limit.max_volume { + violations.push(EvalViolation::VolumetricLimitExceeded); + } + } + // TODO: Implement actual rate limit checking logic + } + + Ok(violations) +} + +pub struct EtherTransfer; +impl Policy for EtherTransfer { + type Grant = Grant; + + type Meaning = Meaning; + + fn analyze(context: &EvalContext) -> Option { + if !context.calldata.is_empty() { + return None; + } + + Some(Meaning { + to: context.to, + value: context.value, + }) + } + + async fn evaluate( + meaning: &Self::Meaning, + grant: &Self::Grant, + meta: &GrantMetadata, + db: &mut impl AsyncConnection, + ) -> QueryResult> { + let mut violations = Vec::new(); + + // Check if the target address is within the grant's allowed targets + if !grant.target.contains(&meaning.to) { + violations.push(EvalViolation::InvalidTarget { target: meaning.to }); + } + + let rate_violations = check_rate_limits(grant, meta, db).await?; + violations.extend(rate_violations); + + Ok(violations) + } + + async fn create_grant( + basic: &models::EvmBasicGrant, + grant: &Self::Grant, + conn: &mut impl AsyncConnection, + ) -> diesel::result::QueryResult { + let grant_id: i32 = insert_into(evm_ether_transfer_grant::table) + .values(&NewEvmEtherTransferGrant { + basic_grant_id: basic.id, + }) + .returning(evm_ether_transfer_grant::id) + .get_result(conn) + .await?; + + for target in &grant.target { + insert_into(evm_ether_transfer_grant_target::table) + .values(NewEvmEtherTransferGrantTarget { + grant_id, + address: target.to_vec(), + }) + .execute(conn) + .await?; + } + + for limit in &grant.limits { + insert_into(evm_ether_transfer_volume_limit::table) + .values(NewEvmEtherTransferVolumeLimit { + grant_id, + window_secs: limit.window.as_secs() as i32, + max_volume: utils::u256_to_bytes(limit.max_volume).to_vec(), + }) + .execute(conn) + .await?; + } + + Ok(grant_id) + } + + async fn try_find_grant( + context: &EvalContext, + conn: &mut impl AsyncConnection, + ) -> diesel::result::QueryResult> { + use crate::db::schema::{ + evm_basic_grant, evm_ether_transfer_grant, evm_ether_transfer_grant_target, + }; + + let target_bytes = context.to.to_vec(); + + // Find a grant where: + // 1. The basic grant's wallet_id and client_id match the context + // 2. Any of the grant's targets match the context's `to` address + let grant: Option = evm_ether_transfer_grant::table + .inner_join( + evm_basic_grant::table + .on(evm_ether_transfer_grant::basic_grant_id.eq(evm_basic_grant::id)), + ) + .inner_join( + evm_ether_transfer_grant_target::table + .on(evm_ether_transfer_grant::id.eq(evm_ether_transfer_grant_target::grant_id)), + ) + .filter(evm_basic_grant::wallet_id.eq(context.wallet_id)) + .filter(evm_basic_grant::client_id.eq(context.client_id)) + .filter(evm_ether_transfer_grant_target::address.eq(&target_bytes)) + .select(EvmEtherTransferGrant::as_select()) + .first::(conn) + .await + .optional()?; + + let Some(grant) = grant else { + return Ok(None); + }; + + use crate::db::schema::evm_ether_transfer_volume_limit; + + // Load grant targets + let target_bytes: Vec = evm_ether_transfer_grant_target::table + .select(EvmEtherTransferGrantTarget::as_select()) + .filter(evm_ether_transfer_grant_target::grant_id.eq(grant.id)) + .load(conn) + .await?; + + // Load volume limits + let limit_rows: Vec = evm_ether_transfer_volume_limit::table + .filter(evm_ether_transfer_volume_limit::grant_id.eq(grant.id)) + .select(EvmEtherTransferVolumeLimit::as_select()) + .load(conn) + .await?; + + // Convert bytes back to Address + let targets: Vec
= target_bytes + .into_iter() + .filter_map(|target| { + // TODO: Handle invalid addresses more gracefully + let arr: [u8; 20] = target.address.try_into().ok()?; + Some(Address::from(arr)) + }) + .collect(); + + // Convert database rows to VolumeLimit + let limits: Vec = limit_rows + .into_iter() + .filter_map(|limit| { + // TODO: Handle invalid volumes more gracefully + let max_volume = utils::bytes_to_u256(&limit.max_volume)?; + Some(VolumeLimit { + window: Duration::from_secs(limit.window_secs as u64), + max_volume, + }) + }) + .collect(); + + let domain_grant = Grant { + target: targets, + limits, + }; + + Ok(Some(( + domain_grant, + GrantMetadata { + basic_grant_id: grant.basic_grant_id, + policy_grant_id: grant.id, + }, + ))) + } + + async fn record_transaction( + context: &EvalContext, + grant: &GrantMetadata, + conn: &mut impl AsyncConnection, + ) -> diesel::result::QueryResult<()> { + use crate::db::schema::evm_ether_transfer_log; + + insert_into(evm_ether_transfer_log::table) + .values(models::NewEvmEtherTransferLog { + grant_id: grant.policy_grant_id, + value: utils::u256_to_bytes(context.value).to_vec(), + client_id: context.client_id, + wallet_id: context.wallet_id, + chain_id: context.chain as i32, + recipient_address: context.to.to_vec(), + }) + .execute(conn) + .await?; + + Ok(()) + } +} diff --git a/server/crates/arbiter-server/src/evm/safe_signer.rs b/server/crates/arbiter-server/src/evm/safe_signer.rs new file mode 100644 index 0000000..952ebe6 --- /dev/null +++ b/server/crates/arbiter-server/src/evm/safe_signer.rs @@ -0,0 +1,184 @@ +use std::sync::Mutex; + +use alloy::{ + consensus::SignableTransaction, + network::{TxSigner, TxSignerSync}, + primitives::{Address, ChainId, Signature, B256}, + signers::{Error, Result, Signer, SignerSync, utils::secret_key_to_address}, +}; +use async_trait::async_trait; +use k256::ecdsa::{self, signature::hazmat::PrehashSigner, RecoveryId, SigningKey}; +use memsafe::MemSafe; + +/// An Ethereum signer that stores its secp256k1 secret key inside a +/// hardware-protected [`MemSafe`] cell. +/// +/// The underlying memory page is kept non-readable/non-writable at rest. +/// Access is temporarily elevated only for the duration of each signing +/// operation, then immediately revoked. +/// +/// Because [`MemSafe::read`] requires `&mut self` while the [`Signer`] trait +/// requires `&self`, the cell is wrapped in a [`Mutex`]. +pub struct SafeSigner { + key: Mutex>, + address: Address, + chain_id: Option, +} + +impl std::fmt::Debug for SafeSigner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SafeSigner") + .field("address", &self.address) + .field("chain_id", &self.chain_id) + .finish() + } +} + +/// Generates a secp256k1 secret key directly inside a [`MemSafe`] cell. +/// +/// Random bytes are written in-place into protected memory, then validated +/// as a legal scalar on the secp256k1 curve (the scalar must be in +/// `[1, n)` where `n` is the curve order — roughly 1-in-2^128 chance of +/// rejection, but we retry to be correct). +/// +/// Returns the protected key bytes and the derived Ethereum address. +pub fn generate(rng: &mut impl rand::Rng) -> (MemSafe<[u8; 32]>, Address) { + loop { + let mut cell = MemSafe::new([0u8; 32]).expect("MemSafe allocation"); + { + let mut w = cell.write().expect("MemSafe write"); + rng.fill_bytes(w.as_mut()); + } + let reader = cell.read().expect("MemSafe read"); + if let Ok(sk) = SigningKey::from_slice(reader.as_ref()) { + let address = secret_key_to_address(&sk); + drop(reader); + return (cell, address); + } + } +} + +impl SafeSigner { + /// Creates a new `SafeSigner` by moving the signing key into a protected + /// memory region. + pub fn new(key: SigningKey) -> Result { + let address = secret_key_to_address(&key); + let cell = MemSafe::new(key).map_err(Error::other)?; + Ok(Self { + key: Mutex::new(cell), + address, + chain_id: None, + }) + } + + fn sign_hash_inner(&self, hash: &B256) -> Result { + let mut cell = self.key.lock().expect("SafeSigner mutex poisoned"); + let reader = cell.read().map_err(Error::other)?; + let sig: (ecdsa::Signature, RecoveryId) = reader.sign_prehash(hash.as_ref())?; + Ok(sig.into()) + } + + fn sign_tx_inner( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + if let Some(chain_id) = self.chain_id { + if !tx.set_chain_id_checked(chain_id) { + return Err(Error::TransactionChainIdMismatch { + signer: chain_id, + tx: tx.chain_id().unwrap(), + }); + } + } + self.sign_hash_inner(&tx.signature_hash()).map_err(Error::other) + } +} + +#[async_trait] +impl Signer for SafeSigner { + #[inline] + async fn sign_hash(&self, hash: &B256) -> Result { + self.sign_hash_inner(hash) + } + + #[inline] + fn address(&self) -> Address { + self.address + } + + #[inline] + fn chain_id(&self) -> Option { + self.chain_id + } + + #[inline] + fn set_chain_id(&mut self, chain_id: Option) { + self.chain_id = chain_id; + } +} + +impl SignerSync for SafeSigner { + #[inline] + fn sign_hash_sync(&self, hash: &B256) -> Result { + self.sign_hash_inner(hash) + } + + #[inline] + fn chain_id_sync(&self) -> Option { + self.chain_id + } +} + +#[async_trait] +impl TxSigner for SafeSigner { + fn address(&self) -> Address { + self.address + } + + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + self.sign_tx_inner(tx) + } +} + +impl TxSignerSync for SafeSigner { + fn address(&self) -> Address { + self.address + } + + fn sign_transaction_sync( + &self, + tx: &mut dyn SignableTransaction, + ) -> Result { + self.sign_tx_inner(tx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy::signers::local::PrivateKeySigner; + + #[test] + fn sign_and_recover() { + let pk = PrivateKeySigner::random(); + let key = pk.into_credential(); + let signer = SafeSigner::new(key).unwrap(); + let message = b"hello arbiter"; + let sig = signer.sign_message_sync(message).unwrap(); + let recovered = sig.recover_address_from_msg(message).unwrap(); + assert_eq!(recovered, Signer::address(&signer)); + } + + #[test] + fn chain_id_roundtrip() { + let pk = PrivateKeySigner::random(); + let key = pk.into_credential(); + let mut signer = SafeSigner::new(key).unwrap(); + assert_eq!(Signer::chain_id(&signer), None); + signer.set_chain_id(Some(1337)); + assert_eq!(Signer::chain_id(&signer), Some(1337)); + } +} diff --git a/server/crates/arbiter-server/src/evm/utils.rs b/server/crates/arbiter-server/src/evm/utils.rs new file mode 100644 index 0000000..4372282 --- /dev/null +++ b/server/crates/arbiter-server/src/evm/utils.rs @@ -0,0 +1,9 @@ +use alloy::primitives::U256; + +pub fn u256_to_bytes(value: U256) -> [u8; 32] { + value.to_le_bytes() +} +pub fn bytes_to_u256(bytes: &[u8]) -> Option { + let bytes: [u8; 32] = bytes.try_into().ok()?; + Some(U256::from_le_bytes(bytes)) +} diff --git a/server/crates/arbiter-server/src/lib.rs b/server/crates/arbiter-server/src/lib.rs index 59aeb9f..a666bdd 100644 --- a/server/crates/arbiter-server/src/lib.rs +++ b/server/crates/arbiter-server/src/lib.rs @@ -24,6 +24,7 @@ use crate::{ pub mod actors; pub mod context; pub mod db; +pub mod evm; const DEFAULT_CHANNEL_SIZE: usize = 1000;