From 8cabd729445f67238a7e84f1caf3635158ef5009 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Wed, 7 Jun 2023 20:34:13 -0700 Subject: [PATCH] Add secp256k1 and keccak256 host functions --- Cargo.lock | 297 ++++++++++++++++-- Cargo.toml | 2 +- soroban-env-common/env.json | 52 +++ soroban-env-host/Cargo.toml | 2 + soroban-env-host/src/budget.rs | 48 ++- soroban-env-host/src/host.rs | 65 ++-- soroban-env-host/src/host/conversion.rs | 73 +---- soroban-env-host/src/host/crypto.rs | 242 ++++++++++++++ .../src/native_contract/token/contract.rs | 4 +- soroban-env-host/src/test/budget_metering.rs | 14 +- soroban-env-host/src/test/token.rs | 4 +- 11 files changed, 685 insertions(+), 118 deletions(-) create mode 100644 soroban-env-host/src/host/crypto.rs diff --git a/Cargo.lock b/Cargo.lock index 854a9b37d..e2544b3bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -112,6 +118,12 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -127,6 +139,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.13.0" @@ -208,6 +229,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "const-oid" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -234,15 +261,37 @@ dependencies = [ "serde_json", ] +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", - "digest", - "rand_core", + "digest 0.9.0", + "rand_core 0.5.1", "subtle", "zeroize", ] @@ -282,6 +331,16 @@ dependencies = [ "syn 2.0.16", ] +[[package]] +name = "der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derive_arbitrary" version = "1.3.0" @@ -302,6 +361,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + [[package]] name = "dissimilar" version = "1.0.6" @@ -324,13 +395,27 @@ dependencies = [ "fnv", ] +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.1.0", + "spki", +] + [[package]] name = "ed25519" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", ] [[package]] @@ -343,7 +428,7 @@ dependencies = [ "ed25519", "rand", "serde", - "sha2", + "sha2 0.9.9", "zeroize", ] @@ -353,6 +438,25 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -382,6 +486,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fnv" version = "1.0.7" @@ -405,6 +519,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -415,7 +530,18 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -424,6 +550,17 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +[[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 = "hashbrown" version = "0.12.3" @@ -457,6 +594,15 @@ dependencies = [ "serde", ] +[[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 = "humantime" version = "2.1.0" @@ -570,6 +716,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.6", + "signature 2.1.0", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -805,6 +974,16 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -869,10 +1048,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", "rand_chacha", - "rand_core", + "rand_core 0.5.1", "rand_hc", ] @@ -883,7 +1062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -892,7 +1071,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", ] [[package]] @@ -901,7 +1089,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -927,6 +1115,16 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rgb" version = "0.8.36" @@ -957,6 +1155,20 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "sec1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "serde" version = "1.0.163" @@ -1022,19 +1234,50 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[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 = "signature" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "simba" version = "0.8.1" @@ -1089,6 +1332,7 @@ dependencies = [ "expect-test", "hex", "itertools", + "k256", "linregress", "log", "more-asserts", @@ -1098,7 +1342,8 @@ dependencies = [ "perf-event", "rand", "rand_chacha", - "sha2", + "sha2 0.9.9", + "sha3", "soroban-env-common", "soroban-native-sdk-macros", "soroban-synth-wasm", @@ -1181,6 +1426,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1190,7 +1445,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stellar-xdr" version = "0.0.16" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=3429ea634df58aadf836790de8bbea21b7e82651#3429ea634df58aadf836790de8bbea21b7e82651" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=eafa8b74e3814d738e2bd948b556f8f3c8a76659#eafa8b74e3814d738e2bd948b556f8f3c8a76659" dependencies = [ "arbitrary", "base64 0.13.1", @@ -1502,6 +1757,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.86" @@ -1717,9 +1978,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 60ece3a13..03e74701d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ soroban-native-sdk-macros = { version = "0.0.16", path = "soroban-native-sdk-mac [workspace.dependencies.stellar-xdr] version = "0.0.16" git = "https://github.com/stellar/rs-stellar-xdr" -rev = "3429ea634df58aadf836790de8bbea21b7e82651" +rev = "eafa8b74e3814d738e2bd948b556f8f3c8a76659" default-features = false [workspace.dependencies.wasmi] diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json index bf0ade174..5f472a0b8 100644 --- a/soroban-env-common/env.json +++ b/soroban-env-common/env.json @@ -1555,6 +1555,58 @@ } ], "return": "Void" + }, + { + "export": "1", + "name": "compute_hash_keccak256", + "args": [ + { + "name": "x", + "type": "BytesObject" + } + ], + "return": "BytesObject", + "docs": "Returns the keccak256 hash of given input bytes." + }, + { + "export": "2", + "name": "verify_sig_ecdsa_secp256k1", + "args": [ + { + "name": "public_key", + "type": "BytesObject" + }, + { + "name": "message", + "type": "BytesObject" + }, + { + "name": "signature", + "type": "BytesObject" + } + ], + "return": "Void", + "docs": "Verifies that a given SEC-1-encoded ECDSA secp256k1 public key, signing a given message, produced a given 64-byte signature." + }, + { + "export": "3", + "name": "recover_key_ecdsa_secp256k1", + "args": [ + { + "name": "msg_digest", + "type": "BytesObject" + }, + { + "name": "signature", + "type": "BytesObject" + }, + { + "name": "recovery_id", + "type": "U32Val" + } + ], + "return": "BytesObject", + "docs": "Recovers the SEC-1-encoded ECDSA secp256k1 public key that produced a given 64-byte signature over a given 32-byte message digest, for a given recovery_id byte." } ] }, diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml index dd845dcca..e53fe70f3 100644 --- a/soroban-env-host/Cargo.toml +++ b/soroban-env-host/Cargo.toml @@ -29,6 +29,8 @@ num-integer = "0.1.45" num-derive = "0.3.3" log = "0.4.17" backtrace = "0.3" +k256 = {version = "0.13.1", features=["ecdsa", "arithmetic"]} +sha3 = "0.10.8" [dev-dependencies] env_logger = "0.9.0" diff --git a/soroban-env-host/src/budget.rs b/soroban-env-host/src/budget.rs index 3c8bd3285..f34bb3ab3 100644 --- a/soroban-env-host/src/budget.rs +++ b/soroban-env-host/src/budget.rs @@ -304,12 +304,17 @@ impl BudgetImpl { ContractCostType::MapEntry => (), ContractCostType::VecEntry => (), ContractCostType::GuardFrame => (), - ContractCostType::VerifyEd25519Sig => self.tracker[i].1 = Some(0), // length of the signature buffer + ContractCostType::VerifyEd25519Sig => self.tracker[i].1 = Some(0), // length of the signed message ContractCostType::VmMemRead => self.tracker[i].1 = Some(0), // number of bytes in the linear memory to read ContractCostType::VmMemWrite => self.tracker[i].1 = Some(0), // number of bytes in the linear memory to write ContractCostType::VmInstantiation => self.tracker[i].1 = Some(0), // length of the wasm bytes, ContractCostType::InvokeVmFunction => (), ContractCostType::ChargeBudget => (), + ContractCostType::ComputeKeccak256Hash => self.tracker[i].1 = Some(0), // number of bytes in the buffer + ContractCostType::ComputeEcdsaSecp256k1Key => (), + ContractCostType::ComputeEcdsaSecp256k1Sig => (), + ContractCostType::VerifyEcdsaSecp256k1Sig => self.tracker[i].1 = Some(0), // length of the signed message + ContractCostType::RecoverEcdsaSecp256k1Key => (), } } } @@ -804,6 +809,27 @@ impl Default for BudgetImpl { cpu.const_term = 130; cpu.linear_term = 0; } + // TODO: these are not yet calibrated, currently all copies of ed25519 and sha256 costs. + ContractCostType::ComputeKeccak256Hash => { + cpu.const_term = 1725; + cpu.linear_term = 33; + } + ContractCostType::ComputeEcdsaSecp256k1Key => { + cpu.const_term = 25551; + cpu.linear_term = 0; + } + ContractCostType::ComputeEcdsaSecp256k1Sig => { + cpu.const_term = 25551; + cpu.linear_term = 0; + } + ContractCostType::VerifyEcdsaSecp256k1Sig => { + cpu.const_term = 369634; + cpu.linear_term = 21; + } + ContractCostType::RecoverEcdsaSecp256k1Key => { + cpu.const_term = 369634; + cpu.linear_term = 0; + } } // define the memory cost model parameters @@ -897,6 +923,26 @@ impl Default for BudgetImpl { mem.const_term = 0; mem.linear_term = 0; } + ContractCostType::ComputeKeccak256Hash => { + mem.const_term = 0; + mem.linear_term = 0; + } + ContractCostType::ComputeEcdsaSecp256k1Key => { + mem.const_term = 0; + mem.linear_term = 0; + } + ContractCostType::ComputeEcdsaSecp256k1Sig => { + mem.const_term = 0; + mem.linear_term = 0; + } + ContractCostType::VerifyEcdsaSecp256k1Sig => { + mem.const_term = 0; + mem.linear_term = 0; + } + ContractCostType::RecoverEcdsaSecp256k1Key => { + mem.const_term = 0; + mem.linear_term = 0; + } } b.init_tracker(); diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index 8848b8c20..d1b227560 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -29,6 +29,7 @@ use crate::{EnvBase, Object, RawVal, Symbol}; pub(crate) mod comparison; mod conversion; +mod crypto; mod data_helper; pub(crate) mod declared_size; pub(crate) mod error; @@ -568,27 +569,6 @@ impl Host { Ok(hash_obj) } - pub(crate) fn verify_sig_ed25519_internal( - &self, - payload: &[u8], - public_key: &ed25519_dalek::PublicKey, - sig: &ed25519_dalek::Signature, - ) -> Result<(), HostError> { - use ed25519_dalek::Verifier; - self.charge_budget( - ContractCostType::VerifyEd25519Sig, - Some(payload.len() as u64), - )?; - public_key.verify(payload, sig).map_err(|_| { - self.err( - ScErrorType::Crypto, - ScErrorCode::InvalidInput, - "failed ED25519 verification", - &[], - ) - }) - } - // Returns the recorded per-address authorization payloads that would cover the // top-level contract function invocation in the enforcing mode. // This should only be called in the recording authorization mode, i.e. only @@ -1860,7 +1840,7 @@ impl VmCallerEnv for Host { address: self.visit_obj(deployer, |addr: &ScAddress| { addr.metered_clone(self.budget_ref()) })?, - salt: self.uint256_from_bytesobj_input("contract_id_salt", salt)?, + salt: self.u256_from_bytesobj_input("contract_id_salt", salt)?, }); let executable = ScContractExecutable::WasmRef(self.hash_from_bytesobj_input("wasm_hash", wasm_hash)?); @@ -2394,6 +2374,16 @@ impl VmCallerEnv for Host { self.add_host_object(self.scbytes_from_vec(hash)?) } + // Notes on metering: covered by components. + fn compute_hash_keccak256( + &self, + _vmcaller: &mut VmCaller, + x: BytesObject, + ) -> Result { + let hash = self.keccak256_hash_from_bytesobj_input(x)?; + self.add_host_object(self.scbytes_from_vec(hash)?) + } + // Notes on metering: covered by components. fn verify_sig_ed25519( &self, @@ -2403,13 +2393,42 @@ impl VmCallerEnv for Host { s: BytesObject, ) -> Result { let public_key = self.ed25519_pub_key_from_bytesobj_input(k)?; - let sig = self.signature_from_bytesobj_input("sig", s)?; + let sig = self.ed25519_signature_from_bytesobj_input("sig", s)?; let res = self.visit_obj(x, |payload: &ScBytes| { self.verify_sig_ed25519_internal(payload.as_slice(), &public_key, &sig) }); Ok(res?.into()) } + // Notes on metering: covered by components. + fn verify_sig_ecdsa_secp256k1( + &self, + _vmcaller: &mut VmCaller, + public_key: BytesObject, + message: BytesObject, + signature: BytesObject, + ) -> Result { + let public_key = self.secp2561k_pub_key_from_bytesobj_input(public_key)?; + let sig = self.secp2561k_signature_from_bytesobj_input(signature)?; + let res = self.visit_obj(message, |payload: &ScBytes| { + self.verify_sig_ecdsa_secp256k1_internal(payload.as_slice(), public_key, &sig) + }); + Ok(res?.into()) + } + + fn recover_key_ecdsa_secp256k1( + &self, + _vmcaller: &mut VmCaller, + msg_digest: BytesObject, + signature: BytesObject, + recovery_id: U32Val, + ) -> Result { + let sig = self.secp2561k_signature_from_bytesobj_input(signature)?; + let rid = self.secp256k1_recovery_id_from_u32val(recovery_id)?; + let hash = self.hash_from_bytesobj_input("msg_digest", msg_digest)?; + self.recover_key_ecdsa_secp256k1_internal(&hash, &sig, rid) + } + fn get_ledger_version(&self, _vmcaller: &mut VmCaller) -> Result { self.with_ledger_info(|li| Ok(li.protocol_version.into())) } diff --git a/soroban-env-host/src/host/conversion.rs b/soroban-env-host/src/host/conversion.rs index 7ffe3a9de..bc6ad70d1 100644 --- a/soroban-env-host/src/host/conversion.rs +++ b/soroban-env-host/src/host/conversion.rs @@ -6,8 +6,6 @@ use crate::err; use crate::host_object::{HostMap, HostObject, HostVec}; use crate::xdr::{Hash, LedgerKey, LedgerKeyContractData, ScVal, ScVec, Uint256}; use crate::{xdr::ContractCostType, Host, HostError, RawVal}; -use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH}; -use sha2::{Digest, Sha256}; use soroban_env_common::num::{ i256_from_pieces, i256_into_pieces, u256_from_pieces, u256_into_pieces, }; @@ -65,10 +63,7 @@ impl Host { } } - pub(crate) fn to_u256_from_account( - &self, - account_id: &AccountId, - ) -> Result { + pub(crate) fn u256_from_account(&self, account_id: &AccountId) -> Result { let crate::xdr::PublicKey::PublicKeyTypeEd25519(ed25519) = account_id.metered_clone(&self.0.budget)?.0; Ok(ed25519) @@ -100,7 +95,7 @@ impl Host { self.fixed_length_bytes_from_bytesobj_input::(name, hash) } - pub(crate) fn uint256_from_bytesobj_input( + pub(crate) fn u256_from_bytesobj_input( &self, name: &'static str, u256: BytesObject, @@ -108,23 +103,7 @@ impl Host { self.fixed_length_bytes_from_bytesobj_input::(name, u256) } - pub(crate) fn signature_from_bytes( - &self, - name: &'static str, - bytes: &[u8], - ) -> Result { - self.fixed_length_bytes_from_slice::(name, bytes) - } - - pub(crate) fn signature_from_bytesobj_input( - &self, - name: &'static str, - sig: BytesObject, - ) -> Result { - self.fixed_length_bytes_from_bytesobj_input::(name, sig) - } - - fn fixed_length_bytes_from_slice( + pub(crate) fn fixed_length_bytes_from_slice( &self, name: &'static str, bytes_arr: &[u8], @@ -148,7 +127,7 @@ impl Host { } } - fn fixed_length_bytes_from_bytesobj_input( + pub(crate) fn fixed_length_bytes_from_bytesobj_input( &self, name: &'static str, obj: BytesObject, @@ -161,27 +140,6 @@ impl Host { }) } - pub(crate) fn ed25519_pub_key_from_bytes(&self, bytes: &[u8]) -> Result { - self.charge_budget(ContractCostType::ComputeEd25519PubKey, None)?; - PublicKey::from_bytes(bytes).map_err(|_| { - err!( - self, - (ScErrorType::Crypto, ScErrorCode::InvalidInput), - "invalid ed25519 public key", - bytes - ) - }) - } - - pub fn ed25519_pub_key_from_bytesobj_input( - &self, - k: BytesObject, - ) -> Result { - self.visit_obj(k, |bytes: &ScBytes| { - self.ed25519_pub_key_from_bytes(bytes.as_slice()) - }) - } - pub(crate) fn account_id_from_bytesobj(&self, k: BytesObject) -> Result { self.visit_obj(k, |bytes: &ScBytes| { Ok(AccountId(xdr::PublicKey::PublicKeyTypeEd25519( @@ -190,29 +148,6 @@ impl Host { }) } - pub(crate) fn sha256_hash_from_bytes(&self, bytes: &[u8]) -> Result, HostError> { - self.charge_budget( - ContractCostType::ComputeSha256Hash, - Some(bytes.len() as u64), - )?; - Ok(Sha256::digest(bytes).as_slice().to_vec()) - } - - pub fn sha256_hash_from_bytesobj_input(&self, x: BytesObject) -> Result, HostError> { - self.visit_obj(x, |bytes: &ScBytes| { - let hash = self.sha256_hash_from_bytes(bytes.as_slice())?; - if hash.len() != 32 { - return Err(err!( - self, - (ScErrorType::Object, ScErrorCode::UnexpectedSize), - "expected 32-byte BytesObject for hash, got different size", - hash.len() - )); - } - Ok(hash) - }) - } - /// Converts a [`RawVal`] to an [`ScVal`] and combines it with the currently-executing /// [`ContractID`] to produce a [`Key`], that can be used to access ledger [`Storage`]. // Notes on metering: covered by components. diff --git a/soroban-env-host/src/host/crypto.rs b/soroban-env-host/src/host/crypto.rs new file mode 100644 index 000000000..249de550f --- /dev/null +++ b/soroban-env-host/src/host/crypto.rs @@ -0,0 +1,242 @@ +use crate::{ + err, + xdr::{ContractCostType, Hash, ScBytes, ScErrorCode, ScErrorType}, + BytesObject, Host, HostError, U32Val, +}; +use sha2::Sha256; +use sha3::Keccak256; + +impl Host { + // Ed25519 functions + + pub(crate) fn ed25519_signature_from_bytes( + &self, + name: &'static str, + bytes: &[u8], + ) -> Result { + self.fixed_length_bytes_from_slice::(name, bytes) + } + + pub(crate) fn ed25519_signature_from_bytesobj_input( + &self, + name: &'static str, + sig: BytesObject, + ) -> Result { + self.fixed_length_bytes_from_bytesobj_input::(name, sig) + } + + pub(crate) fn ed25519_pub_key_from_bytes( + &self, + bytes: &[u8], + ) -> Result { + self.charge_budget(ContractCostType::ComputeEd25519PubKey, None)?; + ed25519_dalek::PublicKey::from_bytes(bytes).map_err(|_| { + err!( + self, + (ScErrorType::Crypto, ScErrorCode::InvalidInput), + "invalid ed25519 public key", + bytes + ) + }) + } + + pub fn ed25519_pub_key_from_bytesobj_input( + &self, + k: BytesObject, + ) -> Result { + self.visit_obj(k, |bytes: &ScBytes| { + self.ed25519_pub_key_from_bytes(bytes.as_slice()) + }) + } + + pub(crate) fn verify_sig_ed25519_internal( + &self, + payload: &[u8], + public_key: &ed25519_dalek::PublicKey, + sig: &ed25519_dalek::Signature, + ) -> Result<(), HostError> { + use ed25519_dalek::Verifier; + self.charge_budget( + ContractCostType::VerifyEd25519Sig, + Some(payload.len() as u64), + )?; + public_key.verify(payload, sig).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "failed ED25519 verification", + &[], + ) + }) + } + + // ECDSA secp256k1 functions + + pub(crate) fn secp2561k_pub_key_from_bytesobj_input( + &self, + k: BytesObject, + ) -> Result { + self.charge_budget(ContractCostType::ComputeEcdsaSecp256k1Key, None)?; + self.visit_obj(k, |bytes: &ScBytes| { + k256::PublicKey::from_sec1_bytes(bytes.as_slice()).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA-secp256k1 public key", + &[k.to_raw()], + ) + }) + }) + } + + pub(crate) fn secp2561k_signature_from_bytesobj_input( + &self, + k: BytesObject, + ) -> Result { + use k256::elliptic_curve::scalar::IsHigh; + self.charge_budget(ContractCostType::ComputeEcdsaSecp256k1Sig, None)?; + let sig: k256::ecdsa::Signature = self.visit_obj(k, |bytes: &ScBytes| { + k256::ecdsa::Signature::try_from(bytes.as_slice()).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA-secp256k1 signature", + &[k.to_raw()], + ) + }) + })?; + if sig.s().is_high().into() { + Err(self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "ECDSA-secp256k1 signature 's' part is not normalized to low form", + &[k.to_raw()], + )) + } else { + Ok(sig) + } + } + + pub(crate) fn secp256k1_recovery_id_from_u32val( + &self, + recovery_id: U32Val, + ) -> Result { + let rid32: u32 = u32::from(recovery_id); + if rid32 > k256::ecdsa::RecoveryId::MAX as u32 { + return Err(self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA-secp256k1 recovery ID", + &[recovery_id.to_raw()], + )); + } + k256::ecdsa::RecoveryId::try_from(rid32 as u8).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "invalid ECDSA-secp256k1 recovery ID", + &[recovery_id.to_raw()], + ) + }) + } + + pub(crate) fn verify_sig_ecdsa_secp256k1_internal( + &self, + payload: &[u8], + public_key: k256::PublicKey, + sig: &k256::ecdsa::Signature, + ) -> Result<(), HostError> { + use k256::ecdsa::{signature::Verifier, VerifyingKey}; + self.charge_budget( + ContractCostType::VerifyEcdsaSecp256k1Sig, + Some(payload.len() as u64), + )?; + let verifier: VerifyingKey = public_key.into(); + verifier.verify(payload, sig).map_err(|_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "failed ecdsa verification", + &[], + ) + }) + } + + pub(crate) fn recover_key_ecdsa_secp256k1_internal( + &self, + hash: &Hash, + sig: &k256::ecdsa::Signature, + rid: k256::ecdsa::RecoveryId, + ) -> Result { + let recovered_key = + k256::ecdsa::VerifyingKey::recover_from_prehash(hash.as_slice(), &sig, rid).map_err( + |_| { + self.err( + ScErrorType::Crypto, + ScErrorCode::InvalidInput, + "ECDSA-secp256k1 signature recovery failed", + &[], + ) + }, + )?; + let rk = ScBytes::from(crate::xdr::BytesM::try_from( + recovered_key.to_sec1_bytes().to_vec(), + )?); + self.add_host_object(rk) + } + + // SHA256 functions + + pub(crate) fn sha256_hash_from_bytes(&self, bytes: &[u8]) -> Result, HostError> { + self.charge_budget( + ContractCostType::ComputeSha256Hash, + Some(bytes.len() as u64), + )?; + Ok(::digest(bytes).as_slice().to_vec()) + } + + pub fn sha256_hash_from_bytesobj_input(&self, x: BytesObject) -> Result, HostError> { + self.visit_obj(x, |bytes: &ScBytes| { + let hash = self.sha256_hash_from_bytes(bytes.as_slice())?; + if hash.len() != 32 { + return Err(err!( + self, + (ScErrorType::Object, ScErrorCode::UnexpectedSize), + "expected 32-byte BytesObject for hash, got different size", + hash.len() + )); + } + Ok(hash) + }) + } + + // Keccak256/SHA3 functions + + pub(crate) fn keccak256_hash_from_bytes(&self, bytes: &[u8]) -> Result, HostError> { + self.charge_budget( + ContractCostType::ComputeKeccak256Hash, + Some(bytes.len() as u64), + )?; + Ok(::digest(bytes) + .as_slice() + .to_vec()) + } + + pub(crate) fn keccak256_hash_from_bytesobj_input( + &self, + x: BytesObject, + ) -> Result, HostError> { + self.visit_obj(x, |bytes: &ScBytes| { + let hash = self.keccak256_hash_from_bytes(bytes.as_slice())?; + if hash.len() != 32 { + return Err(err!( + self, + (ScErrorType::Object, ScErrorCode::UnexpectedSize), + "expected 32-byte BytesObject for hash, got different size", + hash.len() + )); + } + Ok(hash) + }) + } +} diff --git a/soroban-env-host/src/native_contract/token/contract.rs b/soroban-env-host/src/native_contract/token/contract.rs index ffe6f95db..c6f9733ba 100644 --- a/soroban-env-host/src/native_contract/token/contract.rs +++ b/soroban-env-host/src/native_contract/token/contract.rs @@ -149,7 +149,7 @@ impl TokenTrait for Token { )?, issuer: BytesN::<32>::try_from_val( e, - &e.bytes_new_from_slice(&e.to_u256_from_account(&asset4.issuer)?.0)?, + &e.bytes_new_from_slice(&e.u256_from_account(&asset4.issuer)?.0)?, )?, }), )?; @@ -165,7 +165,7 @@ impl TokenTrait for Token { )?, issuer: BytesN::<32>::try_from_val( e, - &e.bytes_new_from_slice(&e.to_u256_from_account(&asset12.issuer)?.0)?, + &e.bytes_new_from_slice(&e.u256_from_account(&asset12.issuer)?.0)?, )?, }), )?; diff --git a/soroban-env-host/src/test/budget_metering.rs b/soroban-env-host/src/test/budget_metering.rs index 0bd5c8b62..0c1898d0d 100644 --- a/soroban-env-host/src/test/budget_metering.rs +++ b/soroban-env-host/src/test/budget_metering.rs @@ -247,6 +247,11 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { (1, Some(147)), (47, None), (263, None), + (1, Some(1)), + (1, None), + (1, None), + (1, Some(1)), + (1, None), ]; for ty in ContractCostType::variants() { @@ -255,7 +260,7 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { let actual = format!("{:?}", host.as_budget()); expect![[r#" ===================================================================================================================================================================== - Cpu limit: 40000000; used: 7964683 + Cpu limit: 40000000; used: 8757482 Mem limit: 52428800; used: 218984 ===================================================================================================================================================================== CostType iterations input cpu_insns mem_bytes const_term_cpu lin_term_cpu const_term_mem lin_term_mem @@ -279,7 +284,12 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { VmMemWrite 1 Some(160) 124 0 124 0 0 0 VmInstantiation 1 Some(147) 671595 123751 600447 484 117871 40 InvokeVmFunction 47 None 278522 22842 5926 0 486 0 - ChargeBudget 284 None 36920 0 130 0 0 0 + ChargeBudget 289 None 37570 0 130 0 0 0 + ComputeKeccak256Hash 1 Some(1) 1758 0 1725 33 0 0 + ComputeEcdsaSecp256k1Key 1 None 25551 0 25551 0 0 0 + ComputeEcdsaSecp256k1Sig 1 None 25551 0 25551 0 0 0 + VerifyEcdsaSecp256k1Sig 1 Some(1) 369655 0 369634 21 0 0 + RecoverEcdsaSecp256k1Key 1 None 369634 0 369634 0 0 0 ===================================================================================================================================================================== "#]] diff --git a/soroban-env-host/src/test/token.rs b/soroban-env-host/src/test/token.rs index e703f0f68..4ce4a27d5 100644 --- a/soroban-env-host/src/test/token.rs +++ b/soroban-env-host/src/test/token.rs @@ -2332,7 +2332,7 @@ fn test_wrapped_asset_classic_balance_boundaries( Asset::CreditAlphanum12(AlphaNum12 { asset_code: AssetCode12([255; 12]), issuer: AccountId(PublicKey::PublicKeyTypeEd25519( - test.host.to_u256_from_account(&issuer_id).unwrap(), + test.host.u256_from_account(&issuer_id).unwrap(), )), }), ); @@ -2489,7 +2489,7 @@ fn test_classic_transfers_not_possible_for_unauthorized_asset() { Asset::CreditAlphanum4(AlphaNum4 { asset_code: AssetCode4([255; 4]), issuer: AccountId(PublicKey::PublicKeyTypeEd25519( - test.host.to_u256_from_account(&issuer_id).unwrap(), + test.host.u256_from_account(&issuer_id).unwrap(), )), }), );