diff --git a/.devcontainer/devcontainer-build.json b/.devcontainer/devcontainer-build.json index df88d659340..f13e5946563 100644 --- a/.devcontainer/devcontainer-build.json +++ b/.devcontainer/devcontainer-build.json @@ -59,7 +59,7 @@ }, "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers/features/node:1": { - "version": 20, + "version": 24, "installYarnUsingApt": false }, "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}, diff --git a/.pnp.cjs b/.pnp.cjs index c1f40457d98..8d3d5332a00 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2831,7 +2831,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/wasm-sdk", "workspace:packages/wasm-sdk"],\ ["@types/chai", "npm:4.3.20"],\ ["@types/mocha", "npm:10.0.10"],\ - ["@types/node", "npm:20.19.30"],\ + ["@types/node", "npm:22.20.5"],\ ["@types/sinon", "npm:9.0.11"],\ ["@types/sinon-chai", "npm:3.2.5"],\ ["assert", "npm:2.0.0"],\ @@ -3158,7 +3158,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/dpns-contract", "workspace:packages/dpns-contract"],\ ["@dashevo/wasm-dpp", "workspace:packages/wasm-dpp"],\ ["@types/bs58", "npm:4.0.4"],\ - ["@types/node", "npm:20.19.30"],\ + ["@types/node", "npm:22.20.5"],\ ["@yarnpkg/pnpify", "npm:4.0.0-rc.42"],\ ["ajv", "npm:8.18.0"],\ ["assert", "npm:2.0.0"],\ @@ -3211,7 +3211,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/wasm-dpp2", "workspace:packages/wasm-dpp2"],\ ["@types/chai", "npm:4.3.20"],\ ["@types/mocha", "npm:10.0.10"],\ - ["@types/node", "npm:20.19.30"],\ + ["@types/node", "npm:22.20.5"],\ ["assert", "npm:2.0.0"],\ ["bs58", "npm:4.0.1"],\ ["buffer", "npm:6.0.3"],\ @@ -3249,7 +3249,7 @@ const RAW_RUNTIME_STATE = ["@dashevo/wasm-sdk", "workspace:packages/wasm-sdk"],\ ["@types/chai", "npm:4.3.20"],\ ["@types/mocha", "npm:10.0.10"],\ - ["@types/node", "npm:20.19.30"],\ + ["@types/node", "npm:22.20.5"],\ ["assert", "npm:2.0.0"],\ ["buffer", "npm:6.0.3"],\ ["chai", "npm:4.3.10"],\ @@ -5263,10 +5263,10 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ - ["npm:20.19.30", {\ - "packageLocation": "./.yarn/cache/@types-node-npm-20.19.30-e3d3d7af6e-4a25e5cbcd.zip/node_modules/@types/node/",\ + ["npm:22.20.5", {\ + "packageLocation": "./.yarn/cache/@types-node-npm-22.20.5-998a48d6f3-a223a73e00.zip/node_modules/@types/node/",\ "packageDependencies": [\ - ["@types/node", "npm:20.19.30"],\ + ["@types/node", "npm:22.20.5"],\ ["undici-types", "npm:6.21.0"]\ ],\ "linkType": "HARD"\ @@ -9640,7 +9640,7 @@ const RAW_RUNTIME_STATE = ["@types/chai", "npm:4.3.20"],\ ["@types/dirty-chai", "npm:2.0.2"],\ ["@types/mocha", "npm:10.0.10"],\ - ["@types/node", "npm:20.19.30"],\ + ["@types/node", "npm:22.20.5"],\ ["@types/sinon", "npm:9.0.11"],\ ["@types/sinon-chai", "npm:3.2.5"],\ ["@yarnpkg/pnpify", "npm:4.0.0-rc.42"],\ @@ -21613,7 +21613,7 @@ const RAW_RUNTIME_STATE = ["@tsconfig/node12", "npm:1.0.9"],\ ["@tsconfig/node14", "npm:1.0.1"],\ ["@tsconfig/node16", "npm:1.0.2"],\ - ["@types/node", "npm:20.19.30"],\ + ["@types/node", "npm:22.20.5"],\ ["@types/swc__core", null],\ ["@types/swc__wasm", null],\ ["@types/typescript", null],\ diff --git a/.yarn/cache/@types-node-npm-20.19.30-e3d3d7af6e-4a25e5cbcd.zip b/.yarn/cache/@types-node-npm-20.19.30-e3d3d7af6e-4a25e5cbcd.zip deleted file mode 100644 index a2ad60a210a..00000000000 Binary files a/.yarn/cache/@types-node-npm-20.19.30-e3d3d7af6e-4a25e5cbcd.zip and /dev/null differ diff --git a/.yarn/cache/@types-node-npm-22.20.5-998a48d6f3-a223a73e00.zip b/.yarn/cache/@types-node-npm-22.20.5-998a48d6f3-a223a73e00.zip new file mode 100644 index 00000000000..865ff1a54ec Binary files /dev/null and b/.yarn/cache/@types-node-npm-22.20.5-998a48d6f3-a223a73e00.zip differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 95020ebedad..f611d580872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +## [Unreleased] + +### Added + +- **Rust compatibility tests:** frozen pre-upgrade vectors for populated GroveDB + state, Core transaction and lock hashes, Platform signatures and encrypted + DashPay fields, stored as constants. + +### Fixed + +- **dpp:** require a WASM-compatible `blst` version for BLS signatures, including + external consumers with older dependency lockfiles. +- **platform-test-suite:** retry proof reads while the local quorum sidecar publishes + a rotated key, with a bounded wait and full proof verification on each attempt. +- **platform-wallet-ffi:** adapt platform node ID derivation to the updated + rust-dashcore API, restoring native SDK builds while preserving canonical bytes. +- **drive-abci:** preserve the fixed 48-byte quorum-key disk encoding independently + of the BLS backend, with historical saved-state and checkpoint regression fixtures. + +### Changed + +- **Rust dependencies:** pin rust-dashcore to `fix/legacy-serde-bls-node-id` + ([rust-dashcore#1149](https://github.com/dashpay/rust-dashcore/pull/1149), + `8fe0a381`), restoring legacy BLS public-key and canonical PlatformNodeId + serialization, and preserving the pre-v4 coinbase payload Serde layout. Adapt + WASM private-key hex decoding to the removal of `dashcore_hashes::hex`. +- **Breaking (Rust features):** remove DPP's `core_key_wallet_bip_38` and SDK's + `core_key_wallet_bip38` feature forwards because upstream removed BIP38 support. + +- **dpp (Rust API migration):** BLS types now live under `dpp::bls::{PublicKey, + SecretKey, Signature, BlsError}`; the generic `dpp::bls_signatures` re-export + is removed. Use `PublicKey::to_bytes()`, `SecretKey::from_be_bytes()` returning + `Option`, and Basic `sign(message)` / `Signature::from_compressed()`. The + `serialization::dashcore::bls_pubkey` Serde adapter remains a deprecated alias. + Serialized encodings and consensus rules are unchanged. See [#5320](https://github.com/dashpay/platform/pull/5320). + ## [5.0.0-beta.2](https://github.com/dashpay/platform/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2026-10-06) diff --git a/Cargo.lock b/Cargo.lock index 3dd3b403830..744c7623efd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,8 +14,8 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", - "generic-array 0.14.7", + "crypto-common 0.1.7", + "generic-array", ] [[package]] @@ -25,10 +25,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", + "zeroize", +] + [[package]] name = "ahash" version = "0.7.8" @@ -478,11 +490,21 @@ checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base58ck" -version = "0.1.100" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec5dc7e09f7bb15f0062da7c03086d6b71a2c84e0af4fccbbc7d8c6559847816" +checksum = "df540278d807651653f9b6f21e18903d790d7769eadfbc0dc4df0a141260318f" dependencies = [ - "bitcoin_hashes", + "bitcoin-internals 0.6.0", + "bitcoin_hashes 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base58ck" +version = "0.5.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "bitcoin-internals 0.7.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "bitcoin_hashes 1.2.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", ] [[package]] @@ -503,15 +525,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64-compat" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" -dependencies = [ - "byteorder", -] - [[package]] name = "base64ct" version = "1.8.3" @@ -568,7 +581,7 @@ dependencies = [ "bitflags 2.13.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -593,7 +606,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.101", "rand 0.8.6", "rand_core 0.6.4", "serde", @@ -631,20 +644,166 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.2.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "bitcoin-internals 0.7.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "hex-conservative 1.3.0", + "serde", +] + +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9daa31138eb443d5751b207f3f64154e2bb09cd59960562ccc7a7112be38147f" +dependencies = [ + "bitcoin-internals 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex-conservative 1.3.0", + "serde", +] + +[[package]] +name = "bitcoin-crypto" +version = "0.3.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "base58ck 0.5.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "bitcoin-internals 0.7.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "bitcoin-network-kind", + "bitcoin-primitives 0.103.1", + "bitcoin_hashes 1.2.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "hex-conservative 1.3.0", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +dependencies = [ + "hex-conservative 0.3.2", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + +[[package]] +name = "bitcoin-internals" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8bea3a9f0cfece4564e37cb49a38cc245ca5184719e50d7d0dda3268722c4e2" + +[[package]] +name = "bitcoin-internals" +version = "0.7.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "serde", +] + [[package]] name = "bitcoin-io" -version = "0.1.100" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding 1.3.0", +] + +[[package]] +name = "bitcoin-network-kind" +version = "1.0.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "bitcoin-internals 0.7.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "serde", +] + +[[package]] +name = "bitcoin-primitives" +version = "0.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e12cb1814d037edbd667554c2c0e6b067db72c83ee935b645a6b5829aca908" +dependencies = [ + "bitcoin-consensus-encoding 1.3.0", + "bitcoin-internals 0.6.0", + "bitcoin-units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin_hashes 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex-conservative 1.3.0", +] + +[[package]] +name = "bitcoin-primitives" +version = "0.103.1" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "bitcoin-consensus-encoding 1.2.0", + "bitcoin-internals 0.7.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "bitcoin-units 0.5.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "bitcoin_hashes 1.2.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "hex-conservative 1.3.0", +] + +[[package]] +name = "bitcoin-units" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" +checksum = "0ae6dcb008158c2bdbe698470280439844be5f640310e4f5d6ef0a0209e066c2" +dependencies = [ + "bitcoin-consensus-encoding 1.3.0", + "bitcoin-internals 0.5.0", + "serde", +] + +[[package]] +name = "bitcoin-units" +version = "0.5.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "bitcoin-consensus-encoding 1.2.0", + "bitcoin-internals 0.7.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "serde", +] [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5304e53726dbe5f93141535e102ed97b5bf4714fbecefdda8f9fb98d7fdaff0e" +dependencies = [ + "bitcoin-consensus-encoding 1.3.0", + "bitcoin-internals 0.6.0", + "hex-conservative 1.3.0", +] + +[[package]] +name = "bitcoin_hashes" +version = "1.2.0" +source = "git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516#7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516" +dependencies = [ + "bitcoin-consensus-encoding 1.2.0", + "bitcoin-internals 0.7.0 (git+https://github.com/rust-bitcoin/rust-bitcoin?rev=7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516)", + "hex-conservative 1.3.0", + "serde", ] [[package]] @@ -677,7 +836,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -711,7 +870,16 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", ] [[package]] @@ -720,7 +888,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -750,41 +918,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" dependencies = [ - "ff", + "ff 0.13.1", "rand_core 0.6.4", "subtle", ] -[[package]] -name = "blsful" -version = "3.0.0" -source = "git+https://github.com/dashpay/agora-blsful?rev=0c34a7a488a0bd1c9a9a2196e793b303ad35c900#0c34a7a488a0bd1c9a9a2196e793b303ad35c900" -dependencies = [ - "anyhow", - "blstrs_plus", - "hex", - "hkdf", - "merlin", - "pairing", - "rand 0.8.6", - "rand_chacha 0.3.1", - "rand_core 0.6.4", - "serde", - "serde_bare", - "sha2", - "sha3", - "subtle", - "thiserror 2.0.18", - "uint-zigzag", - "vsss-rs", - "zeroize", -] - [[package]] name = "blst" -version = "0.3.12" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62dc83a094a71d43eeadd254b1ec2d24cb6a0bb6cadce00df51f0db594711a32" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" dependencies = [ "cc", "glob", @@ -792,24 +935,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "blstrs_plus" -version = "0.8.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a16dd4b0d6b4538e1fa0388843acb186363082713a8fc8416d802a04d013818" -dependencies = [ - "arrayref", - "blst", - "elliptic-curve", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "serde", - "subtle", - "zeroize", -] - [[package]] name = "borsh" version = "1.6.1" @@ -840,7 +965,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2", "tinyvec", ] @@ -938,7 +1062,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -1025,7 +1149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] @@ -1048,7 +1172,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20 0.9.1", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -1129,11 +1253,21 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.7", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -1221,7 +1355,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1357,6 +1491,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4df6f98652d30167eaeea34d77b730e07c8caba6df17bd4551842b9b8da01deb" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1491,27 +1631,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] -name = "crypto-bigint" -version = "0.5.5" +name = "crypto-common" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ - "generic-array 0.14.7", + "generic-array", "rand_core 0.6.4", - "serdect", - "subtle", - "zeroize", + "typenum", ] [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "typenum", + "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -1523,8 +1660,25 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", - "fiat-crypto", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rand_core 0.10.1", "rustc_version", "subtle", "zeroize", @@ -1654,7 +1808,7 @@ dependencies = [ [[package]] name = "dash-network" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "cbindgen 0.29.4", "grovedb-bincode", @@ -1665,11 +1819,45 @@ dependencies = [ [[package]] name = "dash-network-seeds" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "dash-network", ] +[[package]] +name = "dash-num" +version = "0.1.0-beta" +source = "git+https://github.com/dashpay/base-sdk?rev=e6402ced257c370a586ade9840ebbf545a4b7926#e6402ced257c370a586ade9840ebbf545a4b7926" +dependencies = [ + "bitcoin-consensus-encoding 1.3.0", + "dash-types", + "hex-conservative 1.3.0", + "serde", +] + +[[package]] +name = "dash-pkc" +version = "0.1.0-beta" +source = "git+https://github.com/dashpay/base-sdk?rev=e6402ced257c370a586ade9840ebbf545a4b7926#e6402ced257c370a586ade9840ebbf545a4b7926" +dependencies = [ + "aes 0.9.3", + "base58ck 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin_hashes 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "blst", + "cfg-if", + "dash-num", + "dash-types", + "ed25519-dalek 3.0.0", + "ff 0.14.0", + "group 0.14.0", + "hex-conservative 1.3.0", + "rand_core 0.10.1", + "serde", + "sha2 0.11.0", + "subtle", + "zeroize", +] + [[package]] name = "dash-platform-balance-checker" version = "5.0.0-beta.2" @@ -1760,7 +1948,7 @@ dependencies = [ [[package]] name = "dash-spv" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "async-trait", "chrono", @@ -1770,10 +1958,10 @@ dependencies = [ "dashcore_hashes", "futures", "git-state", - "hex", + "hex-conservative 1.3.0", "key-wallet", "key-wallet-manager", - "rand 0.8.6", + "rand 0.9.4", "rayon", "serde", "serde_json", @@ -1786,26 +1974,70 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "dash-types" +version = "0.1.0-beta" +source = "git+https://github.com/dashpay/base-sdk?rev=e6402ced257c370a586ade9840ebbf545a4b7926#e6402ced257c370a586ade9840ebbf545a4b7926" +dependencies = [ + "bitcoin-consensus-encoding 1.3.0", + "bitcoin-primitives 0.103.0", + "cfg-if", + "dash-types-marker", + "hex-conservative 1.3.0", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "dash-types-marker" +version = "0.1.0-beta" +source = "git+https://github.com/dashpay/base-sdk?rev=e6402ced257c370a586ade9840ebbf545a4b7926#e6402ced257c370a586ade9840ebbf545a4b7926" +dependencies = [ + "quote", + "syn 2.0.117", + "xxhash-rust", +] + [[package]] name = "dashcore" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "anyhow", - "base64-compat", + "base64 0.22.1", "bech32 0.9.1", "bitvec", "blake3", - "blsful", "dash-network", + "dash-types", + "dashcore-crypto", "dashcore-private", "dashcore_hashes", - "ed25519-dalek", "grovedb-bincode", "grovedb-bincode-derive", - "hex", - "hex_lit", - "rustversion", + "hex-conservative 1.3.0", + "secp256k1", + "serde", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "dashcore-crypto" +version = "0.45.0" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" +dependencies = [ + "base58ck 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitcoin-crypto", + "dash-network", + "dash-pkc", + "dash-types", + "dashcore-private", + "dashcore_hashes", + "grovedb-bincode", + "grovedb-bincode-derive", + "hex-conservative 1.3.0", "secp256k1", "serde", "thiserror 2.0.18", @@ -1815,15 +2047,15 @@ dependencies = [ [[package]] name = "dashcore-private" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" [[package]] name = "dashcore-rpc" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "dashcore-rpc-json", - "hex", + "hex-conservative 1.3.0", "jsonrpc", "serde", "serde_json", @@ -1833,11 +2065,11 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "dashcore", "grovedb-bincode", - "hex", + "hex-conservative 1.3.0", "key-wallet", "serde", "serde_json", @@ -1848,10 +2080,11 @@ dependencies = [ [[package]] name = "dashcore_hashes" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ - "dashcore-private", + "bitcoin_hashes 0.14.101", "grovedb-bincode", + "hex-conservative 1.3.0", "rs-x11-hash", "serde", ] @@ -1910,7 +2143,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" dependencies = [ - "aes", + "aes 0.8.4", "block-padding", "cbc", "dbus", @@ -1919,7 +2152,7 @@ dependencies = [ "num", "once_cell", "openssl", - "sha2", + "sha2 0.10.9", "zeroize", ] @@ -2046,11 +2279,21 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + [[package]] name = "displaydoc" version = "0.2.6" @@ -2112,11 +2355,13 @@ dependencies = [ "async-trait", "base64 0.22.1", "bech32 0.11.1", + "blst", "bs58", "byteorder", "chrono", "chrono-tz", "ciborium", + "dash-pkc", "dash-spv", "dashcore", "dashcore-rpc", @@ -2124,8 +2369,11 @@ dependencies = [ "derive_more 1.0.0", "dpp", "dpp-json-convertible-derive", + "ed25519-dalek 2.2.0", "env_logger", "getrandom 0.2.17", + "getrandom 0.3.4", + "getrandom 0.4.2", "grovedb-bincode", "grovedb-commitment-tree", "hex", @@ -2155,11 +2403,12 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "sha2", + "sha2 0.10.9", "strum 0.26.3", "thiserror 2.0.18", "tokio", "tracing", + "zeroize", ] [[package]] @@ -2337,7 +2586,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature", + "signature 2.2.0", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "signature 3.0.0", ] [[package]] @@ -2346,55 +2604,35 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "curve25519-dalek", - "ed25519", + "curve25519-dalek 4.1.3", + "ed25519 2.2.3", "rand_core 0.6.4", "serde", - "sha2", + "sha2 0.10.9", "subtle", "zeroize", ] [[package]] -name = "either" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" +name = "ed25519-dalek" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array 0.14.7", - "group", - "hkdf", - "pkcs8", - "rand_core 0.6.4", - "sec1", +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek 5.0.0", + "ed25519 3.0.0", + "rand_core 0.10.1", + "sha2 0.11.0", + "signature 3.0.0", "subtle", - "tap", "zeroize", ] [[package]] -name = "elliptic-curve-tools" -version = "0.2.0" +name = "either" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de2b6fae800f08032a6ea32995b52925b1d451bff9d445c8ab2932323277faf" -dependencies = [ - "elliptic-curve", - "heapless 0.8.0", - "hex", - "multiexp", - "serde", - "zeroize", -] +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embedded-io" @@ -2492,7 +2730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2553,7 +2791,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2576,12 +2814,28 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "file-rotate" version = "0.7.6" @@ -2674,7 +2928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26c4b37de5ae15812a764c958297cfc50f5c010438f60c6ce75d11b802abd404" dependencies = [ "cbc", - "cipher", + "cipher 0.4.4", "libm", "num-bigint", "num-integer", @@ -2843,18 +3097,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", -] - -[[package]] -name = "generic-array" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351" -dependencies = [ - "rustversion", - "serde_core", - "typenum", ] [[package]] @@ -2891,11 +3133,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -2922,7 +3166,7 @@ dependencies = [ [[package]] name = "git-state" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" [[package]] name = "glob" @@ -2961,11 +3205,20 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.1", "memuse", - "rand 0.8.6", "rand_core 0.6.4", - "rand_xorshift 0.3.0", + "subtle", +] + +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff 0.14.0", + "rand_core 0.10.1", "subtle", ] @@ -2999,7 +3252,7 @@ dependencies = [ "intmap", "itertools 0.14.0", "reqwest 0.13.4", - "sha2", + "sha2 0.10.9", "tempfile", "thiserror 2.0.18", "tokio", @@ -3275,8 +3528,8 @@ checksum = "fb2a697cad929f706b7987fe804ad57d43622cd37463ba7e4d662a926fdcfea3" dependencies = [ "arrayvec", "bitvec", - "ff", - "group", + "ff 0.13.1", + "group 0.13.0", "halo2_poseidon", "halo2_proofs", "lazy_static", @@ -3300,8 +3553,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa3da60b81f02f9b33ebc6252d766f843291fb4d2247a07ae73d20b791fc56f" dependencies = [ "bitvec", - "ff", - "group", + "ff 0.13.1", + "group 0.13.0", "pasta_curves", ] @@ -3312,8 +3565,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05713f117155643ce10975e0bee44a274bcda2f4bb5ef29a999ad67c1fa8d4d3" dependencies = [ "blake2b_simd", - "ff", - "group", + "ff 0.13.1", + "group 0.13.0", "halo2_legacy_pdqsort", "indexmap 1.9.3", "maybe-rayon", @@ -3331,15 +3584,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -3404,20 +3648,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", - "hash32 0.2.1", + "hash32", "rustc_version", "serde", - "spin 0.9.8", - "stable_deref_trait", -] - -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "hash32 0.3.1", + "spin", "stable_deref_trait", ] @@ -3444,9 +3678,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hex-conservative" @@ -3458,16 +3689,28 @@ dependencies = [ ] [[package]] -name = "hex-literal" -version = "1.1.0" +name = "hex-conservative" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" +checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" +dependencies = [ + "arrayvec", +] [[package]] -name = "hex_lit" -version = "0.1.1" +name = "hex-conservative" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271e0d19bcb473b6675739a2b536076b24a082316cb5199ad918edce10c599e8" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-literal" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "hkdf" @@ -3484,7 +3727,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -3563,6 +3806,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.10.1" @@ -3866,7 +4118,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", - "generic-array 0.14.7", + "generic-array", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", ] [[package]] @@ -3898,7 +4159,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4136,45 +4397,30 @@ checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" dependencies = [ "bitvec", "bls12_381", - "ff", - "group", + "ff 0.13.1", + "group 0.13.0", "rand_core 0.6.4", "subtle", ] -[[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 = "key-wallet" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ - "aes", "async-trait", - "base58ck", "bip39", "bitflags 2.13.0", - "bs58", "dashcore", "dashcore-private", "dashcore_hashes", - "getrandom 0.2.17", + "getrandom 0.4.2", "grovedb-bincode", "grovedb-bincode-derive", - "hex", - "rand 0.8.6", - "scrypt", + "hex-conservative 1.3.0", "secp256k1", "serde", "serde_json", - "sha2", "tracing", "unicode-normalization", "zeroize", @@ -4183,12 +4429,12 @@ dependencies = [ [[package]] name = "key-wallet-ffi" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "cbindgen 0.29.4", "dash-network", "dashcore", - "hex", + "hex-conservative 1.3.0", "key-wallet", "key-wallet-manager", "libc", @@ -4199,11 +4445,12 @@ dependencies = [ [[package]] name = "key-wallet-manager" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=40268cc0402a8933ec539f16b2d634c4e25876ad#40268cc0402a8933ec539f16b2d634c4e25876ad" +source = "git+https://github.com/dashpay/rust-dashcore?rev=8fe0a38170059742ff902acc29a696b72490903c#8fe0a38170059742ff902acc29a696b72490903c" dependencies = [ "async-trait", "dashcore", "grovedb-bincode", + "hex-conservative 1.3.0", "key-wallet", "rayon", "serde", @@ -4238,7 +4485,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -4400,7 +4647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" dependencies = [ "crc", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -4476,18 +4723,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.6.4", - "zeroize", -] - [[package]] name = "metrics" version = "0.24.6" @@ -4654,20 +4889,6 @@ dependencies = [ "pxfm", ] -[[package]] -name = "multiexp" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec2ce93a6f06ac6cae04c1da3f2a6a24fcfc1f0eb0b4e0f3d302f0df45326cb" -dependencies = [ - "ff", - "group", - "rand_core 0.6.4", - "rustversion", - "std-shims", - "zeroize", -] - [[package]] name = "multimap" version = "0.10.1" @@ -4734,7 +4955,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4759,8 +4980,6 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "rand 0.8.6", - "serde", ] [[package]] @@ -4776,8 +4995,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", - "rand 0.8.6", - "serde", ] [[package]] @@ -4826,7 +5043,6 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "serde", ] [[package]] @@ -4974,14 +5190,14 @@ name = "orchard" version = "0.14.0" source = "git+https://github.com/dashpay/orchard.git?tag=dashified-0.14.1#38ac9c19a2df7bf3eeadc22ab23053e8fd538828" dependencies = [ - "aes", + "aes 0.8.4", "bitvec", "blake2b_simd", "corez", - "ff", + "ff 0.13.1", "fpe", "getset", - "group", + "group 0.13.0", "halo2_gadgets", "halo2_poseidon", "halo2_proofs", @@ -5004,15 +5220,6 @@ dependencies = [ "zip32", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "parking" version = "2.2.1" @@ -5069,8 +5276,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" dependencies = [ "blake2b_simd", - "ff", - "group", + "ff 0.13.1", + "group 0.13.0", "lazy_static", "rand 0.8.6", "static_assertions", @@ -5089,7 +5296,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", + "digest 0.10.7", "hmac", ] @@ -5200,11 +5407,11 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" name = "platform-encryption" version = "5.0.0-beta.2" dependencies = [ - "aes", + "aes 0.8.4", "cbc", "hmac", "secp256k1", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", ] @@ -5300,9 +5507,10 @@ dependencies = [ "rusqlite", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "simple-signer", "static_assertions", + "tempfile", "thiserror 1.0.69", "tokio", "tokio-util", @@ -5375,7 +5583,7 @@ dependencies = [ "serde", "serde_json", "serial_test", - "sha2", + "sha2 0.10.9", "static_assertions", "subtle", "tempfile", @@ -5466,7 +5674,7 @@ dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", - "heapless 0.7.17", + "heapless", "serde", ] @@ -5596,7 +5804,7 @@ dependencies = [ "num-traits", "rand 0.9.4", "rand_chacha 0.9.0", - "rand_xorshift 0.4.0", + "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", @@ -5629,8 +5837,8 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ - "heck 0.5.0", - "itertools 0.10.5", + "heck 0.4.1", + "itertools 0.14.0", "log", "multimap", "petgraph", @@ -5651,7 +5859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5664,7 +5872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5840,7 +6048,7 @@ dependencies = [ "once_cell", "socket2 0.5.10", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5946,15 +6154,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core 0.6.4", -] - [[package]] name = "rand_xorshift" version = "0.4.0" @@ -6020,7 +6219,7 @@ dependencies = [ "blake2b_simd", "byteorder", "frost-rerandomized", - "group", + "group 0.13.0", "hex", "jubjub", "pasta_curves", @@ -6388,7 +6587,7 @@ dependencies = [ "serde_bytes", "serde_json", "serial_test", - "sha2", + "sha2 0.10.9", "tempfile", "test-case", "thiserror 2.0.18", @@ -6426,7 +6625,7 @@ dependencies = [ "rand 0.8.6", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "thiserror 2.0.18", "tokio", "tonic-web-wasm-client", @@ -6658,7 +6857,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6671,7 +6870,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6730,7 +6929,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6775,15 +6974,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -6850,17 +7040,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "pbkdf2", - "salsa20", - "sha2", -] - [[package]] name = "sdd" version = "3.0.10" @@ -6873,37 +7052,22 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array 0.14.7", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "secp256k1" -version = "0.30.0" +version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +checksum = "d7f404a8dab7a7a5a631e741d8699aa9c8e1d689fc26ccf897c7187565490b69" dependencies = [ - "bitcoin_hashes", - "rand 0.8.6", + "rand 0.9.4", "secp256k1-sys", "serde", ] [[package]] name = "secp256k1-sys" -version = "0.10.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +checksum = "f6b2992d4a3cd244539a7d5d0966aadbe5ca7fb868a5d7e38c29499b9e709bbd" dependencies = [ "cc", ] @@ -6968,15 +7132,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "serde_bare" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51c55386eed0f1ae957b091dc2ca8122f287b60c79c774cbe3d5f2b69fded660" -dependencies = [ - "serde", -] - [[package]] name = "serde_bytes" version = "0.11.19" @@ -7176,7 +7331,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] @@ -7187,17 +7342,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] -name = "sha3" -version = "0.10.9" +name = "sha2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ - "digest", - "keccak", + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -7252,6 +7408,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -7283,6 +7448,7 @@ dependencies = [ "dpp", "grovedb-bincode", "hex", + "rand 0.8.6", "tracing", "zeroize", ] @@ -7293,7 +7459,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d268ae0ea06faafe1662e9967cd4f9022014f5eeb798e0c302c876df8b7af9c" dependencies = [ - "group", + "group 0.13.0", "pasta_curves", "subtle", ] @@ -7351,12 +7517,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spin" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - [[package]] name = "spki" version = "0.7.3" @@ -7400,17 +7560,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "std-shims" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227c4f8561598188d0df96dbe749824576174bba278b5b6bb2eacff1066067d0" -dependencies = [ - "hashbrown 0.16.1", - "rustversion", - "spin 0.10.0", -] - [[package]] name = "strategy-tests" version = "5.0.0-beta.2" @@ -7592,7 +7741,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8428,12 +8577,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "uint-zigzag" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61faa33dc26b2851a37da5390a1a4cac015887b1e97ecd77ce7b4f987431de9f" - [[package]] name = "unarray" version = "0.1.4" @@ -8479,7 +8622,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -8619,24 +8762,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "vsss-rs" -version = "5.1.0" -source = "git+https://github.com/dashpay/vsss-rs?branch=main#668f1406bf25a4b9a95cd97c9069f7a1632897c3" -dependencies = [ - "crypto-bigint", - "elliptic-curve", - "elliptic-curve-tools", - "generic-array 1.4.3", - "hex", - "num", - "rand_core 0.6.4", - "serde", - "sha3", - "subtle", - "zeroize", -] - [[package]] name = "wait-timeout" version = "0.2.1" @@ -8837,7 +8962,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.5.0", "serde_json", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", "wasm-bindgen", ] @@ -8922,7 +9047,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.5.0", "serde_json", - "sha2", + "sha2 0.10.9", "thiserror 2.0.18", "tokio", "tracing", @@ -9038,7 +9163,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -9478,7 +9603,7 @@ source = "git+https://github.com/dashpay/zcash_note_encryption?rev=9f7e93d#9f7e9 dependencies = [ "chacha20 0.9.1", "chacha20poly1305", - "cipher", + "cipher 0.4.4", "rand_core 0.6.4", "subtle", ] @@ -9535,9 +9660,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "serde", "zeroize_derive", @@ -9616,7 +9741,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" dependencies = [ - "aes", + "aes 0.8.4", "arbitrary", "constant_time_eq 0.3.1", "crc32fast", diff --git a/Cargo.toml b/Cargo.toml index 1236ac5c9ea..2da73394a5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,14 +65,14 @@ grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "9791d2772 grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "9791d277207998b7789c85b0745e99d37040a12e" } grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "9791d277207998b7789c85b0745e99d37040a12e" } grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "9791d277207998b7789c85b0745e99d37040a12e" } -dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } -dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } -dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } -key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } -key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } -key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } -dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "40268cc0402a8933ec539f16b2d634c4e25876ad" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } +dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } +dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } +key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } +key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } +key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } +dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "8fe0a38170059742ff902acc29a696b72490903c" } tokio-metrics = "0.5" # Size-tuned profile for the iOS `rs-unified-sdk-ffi` staticlib, which diff --git a/Dockerfile b/Dockerfile index 6984e3ad1b9..6d7b8f16e9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,7 +72,7 @@ ARG DEPS_IMAGE=deps-${DEPS_IMAGE:-base} # # DEPS: INSTALL AND CACHE DEPENDENCIES # -FROM node:20-alpine${ALPINE_VERSION} AS deps-base +FROM node:22-alpine${ALPINE_VERSION} AS deps-base # # Install some dependencies @@ -828,7 +828,10 @@ RUN yarn workspaces focus --production dashmate # # STAGE: FINAL DASHMATE HELPER IMAGE # -FROM node:20-alpine${ALPINE_VERSION} AS dashmate-helper +# Node 24 is the newest usable LTS line: on Node 26 `cbor` (through +# nofilter 3.1.0) fails every decode with "Insufficient data", which breaks +# js-dapi-client, and the image ships neither Yarn nor Corepack. +FROM node:24-alpine${ALPINE_VERSION} AS dashmate-helper RUN apk add --no-cache docker-cli docker-cli-compose curl @@ -880,7 +883,7 @@ RUN yarn workspaces focus --production @dashevo/platform-test-suite # # STAGE: FINAL TEST SUITE IMAGE # -FROM node:20-alpine${ALPINE_VERSION} AS test-suite +FROM node:22-alpine${ALPINE_VERSION} AS test-suite RUN apk add --no-cache bash diff --git a/book/src/evo-sdk/getting-started.md b/book/src/evo-sdk/getting-started.md index 7dca58e5dda..7c426a9bd1f 100644 --- a/book/src/evo-sdk/getting-started.md +++ b/book/src/evo-sdk/getting-started.md @@ -13,7 +13,7 @@ full type definitions included. In CommonJS projects use a dynamic `import()`: const { EvoSDK } = await import('@dashevo/evo-sdk'); ``` -Requirements: Node.js ≥ 18.18 or any modern browser with WebAssembly support. +Requirements: Node.js ≥ 22 or any modern browser with WebAssembly support. ## Quick start diff --git a/book/src/evo-sdk/networks-and-environments.md b/book/src/evo-sdk/networks-and-environments.md index c6ff4f62673..2a78855a3c6 100644 --- a/book/src/evo-sdk/networks-and-environments.md +++ b/book/src/evo-sdk/networks-and-environments.md @@ -103,7 +103,7 @@ handles platform differences transparently. **Node.js considerations:** -- Requires Node.js ≥ 18.18 (for WebAssembly and `fetch` support) +- Requires Node.js ≥ 22 - ESM-only package — use `import`, not `require` - No additional polyfills needed diff --git a/book/src/evo-sdk/overview.md b/book/src/evo-sdk/overview.md index 646e49ae187..324bac417f0 100644 --- a/book/src/evo-sdk/overview.md +++ b/book/src/evo-sdk/overview.md @@ -3,7 +3,7 @@ The **Evo SDK** (`@dashevo/evo-sdk`) is the primary JavaScript/TypeScript SDK for building applications on Dash Platform. It provides a high-level, strongly-typed facade over the WebAssembly-based Rust SDK, working in both -Node.js (≥ 18.18) and modern browsers. +Node.js (≥ 22) and modern browsers. > **API reference**: For detailed per-method documentation with interactive > examples, see the [Evo SDK Docs](https://dashpay.github.io/evo-sdk-website/docs.html). diff --git a/book/src/getting-started.md b/book/src/getting-started.md index ff9dbc5c46c..f3aeda52ae8 100644 --- a/book/src/getting-started.md +++ b/book/src/getting-started.md @@ -5,7 +5,7 @@ Platform monorepo. ## Prerequisites -- [Node.js](https://nodejs.org/) v20+ +- [Node.js](https://nodejs.org/) v24+ - [Docker](https://docs.docker.com/get-docker/) v20.10+ - [Rust](https://www.rust-lang.org/tools/install) v1.92+, with the wasm32 target: diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index e0ab33d578d..d2714dc5f81 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -3,6 +3,9 @@ "private": true, "version": "5.0.0-beta.2", "description": "Dash Platform benchmark tool", + "engines": { + "node": ">=22" + }, "scripts": { "bench": "node ./bin/bench.js", "lint": "eslint ." diff --git a/packages/dapi/package.json b/packages/dapi/package.json index c96c0c0c4aa..092e016cd8b 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -3,6 +3,9 @@ "private": true, "version": "5.0.0-beta.2", "description": "A decentralized API for the Dash network", + "engines": { + "node": ">=22" + }, "scripts": { "api": "node scripts/api.js", "core-streams": "node scripts/core-streams.js", diff --git a/packages/dashmate/docs/installation.md b/packages/dashmate/docs/installation.md index c367b811cb9..9180a987069 100644 --- a/packages/dashmate/docs/installation.md +++ b/packages/dashmate/docs/installation.md @@ -47,7 +47,7 @@ To install the NPM package, it is necessary to install Node.JS first. We recomme ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash source ~/.bashrc -nvm install 20 +nvm install 24 ``` Once Node.JS has been installed, use NPM to install dashmate: diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 65f1b458a8c..702ad9ddf23 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -48,7 +48,7 @@ } ], "engines": { - "node": ">=20" + "node": ">=24" }, "license": "MIT", "bugs": { diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index bdfd97643f9..c3468491baf 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -72,7 +72,7 @@ "webpack-cli": "^4.9.1" }, "engines": { - "node": ">=18.18" + "node": ">=22" }, "files": [ "docs", diff --git a/packages/js-dash-sdk/README.md b/packages/js-dash-sdk/README.md index e8d7e0d8870..cb065274233 100644 --- a/packages/js-dash-sdk/README.md +++ b/packages/js-dash-sdk/README.md @@ -29,7 +29,7 @@ Dash library for JavaScript/TypeScript ecosystem (Wallet, DAPI, Primitives, BLS, | Wallet / key management | 🟢 Full SPV wallet via wallet-lib | 🟡 Key derivation utilities only (BIP44, WIF, signing) | | Module format | CommonJS + UMD bundle | ESM only | | Browser CDN | 🟢 unpkg (UMD) | 🟢 esm.sh (ESM) | -| Node.js requirement | Any | >= 18.18 | +| Node.js requirement | >= 22 | >= 22 | | TypeScript | 🟢 | 🟢 | **Warning: This SDK should only be used in production when connected to trusted nodes. Although it @@ -52,7 +52,7 @@ Dash library provides access via [DAPI](https://dashplatform.readme.io/docs/expl In order to use this library, you will need to add it to your project as a dependency. -Having [NodeJS](https://nodejs.org/) installed, just type : `npm install dash` in your terminal. +With [Node.js](https://nodejs.org/) >= 22 installed, run `npm install dash` in your terminal. ```sh npm install dash diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index 0e610d0f278..bbd0e66a2ef 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -6,6 +6,9 @@ "unpkg": "dist/dash.min.js", "browser": "dist/dash.min.js", "types": "build/index.d.ts", + "engines": { + "node": ">=22" + }, "scripts": { "start:dev": "nodemon --exec 'yarn run build && yarn run test:unit'", "start:ts": "tsc -p tsconfig.build.json --watch", @@ -57,7 +60,7 @@ "@types/chai": "^4.3.11", "@types/dirty-chai": "^2.0.2", "@types/mocha": "^10.0.6", - "@types/node": "^20.10.0", + "@types/node": "^22.0.0", "@types/sinon": "^9.0.4", "@types/sinon-chai": "^3.2.4", "@yarnpkg/pnpify": "^4.0.0-rc.42", diff --git a/packages/js-evo-sdk/README.md b/packages/js-evo-sdk/README.md index 25fd8dfa1df..134a0c05fd4 100644 --- a/packages/js-evo-sdk/README.md +++ b/packages/js-evo-sdk/README.md @@ -32,7 +32,7 @@ Evo SDK provides a high-level, strongly-typed interface for interacting with [Da npm install @dashevo/evo-sdk ``` -The package is ESM-only (`"type": "module"`). In CommonJS projects, use dynamic `import()`. Requires Node.js >= 18.18. +The package is ESM-only (`"type": "module"`). In CommonJS projects, use dynamic `import()`. Requires Node.js >= 22. ## Usage diff --git a/packages/js-evo-sdk/package.json b/packages/js-evo-sdk/package.json index 9c2b19985c0..e5dc9f6f148 100644 --- a/packages/js-evo-sdk/package.json +++ b/packages/js-evo-sdk/package.json @@ -15,7 +15,7 @@ }, "sideEffects": false, "engines": { - "node": ">=18.18" + "node": ">=22" }, "files": [ "dist/**", @@ -33,7 +33,7 @@ "devDependencies": { "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", - "@types/node": "^20.10.0", + "@types/node": "^22.0.0", "@types/sinon": "^9.0.4", "@types/sinon-chai": "^3.2.4", "assert": "^2.0.0", diff --git a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt index 420ad1a6bbb..5db48d4f6d9 100644 --- a/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt +++ b/packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt @@ -14,6 +14,10 @@ private const val PERSISTER_UNREADABLE_USER_MESSAGE = private const val PERSISTER_UNSAVED_USER_MESSAGE = "The wallet data could not be saved and may need to be restored." +// Display text for a panicked SPV teardown; the native message is the panic text. +private const val SPV_PROCESS_RESTART_REQUIRED_USER_MESSAGE = + "Sync could not be stopped cleanly. Restart the app to use it again." + /** * Public error hierarchy of the Kotlin SDK — the Android analog of the * Swift SDK's `UserFacingError`/`SDKError` split, keyed off the native @@ -615,6 +619,26 @@ sealed class DashSdkError( class ShieldedRecoveryKeysRequired(message: String, cause: Throwable? = null) : PlatformWallet(message, cause) + /** + * `ErrorShutdownIncomplete` (native code 27). A sync pass or an SPV + * teardown was still running; nothing was wiped. Retry the stop or + * clear. An SPV start refused with this needs an SPV stop first. + */ + class ShutdownIncomplete(message: String, cause: Throwable? = null) : + PlatformWallet(message, cause) { + override val isRetryable: Boolean get() = true + } + + /** + * `ErrorSpvProcessRestartRequired` (native code 59). SPV startup or + * teardown panicked. Not retryable: restart the app process. + * [message] is the panic detail; display [userMessage]. + */ + class SpvProcessRestartRequired(message: String, cause: Throwable? = null) : + PlatformWallet(message, cause) { + override val userMessage: String get() = SPV_PROCESS_RESTART_REQUIRED_USER_MESSAGE + } + /** * Any other `PlatformWalletFFIResultCode` without a dedicated type. * Carries the platform-wallet [nativeCode] (already de-offset) and @@ -737,6 +761,7 @@ sealed class DashSdkError( 24 -> PlatformWallet.AssetLockAlreadyConsumed(message, cause) // ErrorAssetLockAlreadyConsumed 25 -> PlatformWallet.AssetLockFundingMismatch(message, cause) // ErrorAssetLockFundingMismatch 26 -> PlatformWallet.TransactionBroadcastRejected(message, cause) // ErrorTransactionBroadcastRejected + 27 -> PlatformWallet.ShutdownIncomplete(message, cause) // ErrorShutdownIncomplete 29 -> PlatformWallet.AssetLockInsufficientFunds(message, cause) // ErrorAssetLockInsufficientFunds // The deferred-token trio sits at the contiguous block 34-36 because // 27-33 are claimed elsewhere: 27 ErrorShutdownIncomplete @@ -808,6 +833,7 @@ sealed class DashSdkError( 55 -> PlatformWallet.ShieldedIdentityDebitPending(message, cause) 56 -> PlatformWallet.ShieldedRecoveryCorrupted(message, cause) 57 -> PlatformWallet.ShieldedRecoveryKeysRequired(message, cause) + 59 -> PlatformWallet.SpvProcessRestartRequired(message, cause) // ErrorSpvProcessRestartRequired else -> // @Deprecated fallback — see the code-6 arm; code 31 is the // real discriminator. diff --git a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt index 7129c55f633..3bb82014a4a 100644 --- a/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt +++ b/packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt @@ -227,6 +227,28 @@ class DashSdkErrorTest { assertFalse("Generic platform-wallet errors are not retryable", mapped.isRetryable) } + @Test + fun spvTeardownCodes27And59MapTypedWithOppositeRetryability() { + val offset = DashSdkError.PLATFORM_WALLET_CODE_OFFSET + + val incomplete = + DashSdkError.fromNative(DashSDKException(offset + 27, "SPV teardown timed out")) + assertTrue(incomplete is DashSdkError.PlatformWallet.ShutdownIncomplete) + assertEquals("SPV teardown timed out", incomplete.message) + assertTrue("a tracked teardown completes on a repeated stop", incomplete.isRetryable) + + val panicDetail = "teardown task 12 panicked with message \"boom\"" + val restart = DashSdkError.fromNative(DashSDKException(offset + 59, panicDetail)) + assertTrue(restart is DashSdkError.PlatformWallet.SpvProcessRestartRequired) + assertEquals(panicDetail, restart.message) + assertFalse("no retry recovers a panicked teardown", restart.isRetryable) + assertEquals( + "the panic detail is diagnostic and must not be the displayed text", + "Sync could not be stopped cleanly. Restart the app to use it again.", + restart.userMessage, + ) + } + @Test fun platformShieldCapacityCode41MapsTyped() { val message = diff --git a/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js b/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js index a49f0088484..b54cde9d23b 100644 --- a/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js +++ b/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js @@ -1,4 +1,8 @@ const DAPIAddress = require('@dashevo/dapi-client/lib/dapiAddressProvider/DAPIAddress'); +const wait = require('../wait'); + +// Allow the sidecar's 60-second refresh and the SDK's initial 60-second node bans to expire. +const QUORUM_PUBLICATION_WAIT_MS = 65000; /** * `WasmSdkError.name` for a transition family whose proof cannot bind the @@ -54,6 +58,30 @@ function toReportableError(error) { return reportable; } +/** + * Re-query and fully verify a proof once the sidecar publishes its quorum key. + * The WASM wait APIs refresh quorum caches on every call; no transition is broadcast here. + * + * @param {Function} readProof + * @returns {Promise<*>} + */ +async function waitForPublishedQuorum(readProof) { + try { + return await readProof(); + } catch (error) { + const reportable = toReportableError(error); + if (reportable.name !== 'DapiClientError' + || !reportable.message.includes( + 'context provider error: invalid quorum: Quorum not found in cache for hash:', + )) { + throw reportable; + } + + await wait(QUORUM_PUBLICATION_WAIT_MS); + return readProof(); + } +} + /** * Shared EvoSDK instance. One per process: the verifier is stateless and the * underlying WASM SDK multiplexes concurrent requests. @@ -222,7 +250,7 @@ function createPlatformProofVerifier({ ); try { - await sdk.stateTransitions.waitForResponse(stateTransition); + await waitForPublishedQuorum(() => sdk.stateTransitions.waitForResponse(stateTransition)); } catch (error) { // Balance top-ups, credit transfers and withdrawals, address funds // movements, shields and no-history token operations have no proof @@ -235,7 +263,9 @@ function createPlatformProofVerifier({ } try { - await sdk.stateTransitions.waitForAffectedState(stateTransition); + await waitForPublishedQuorum( + () => sdk.stateTransitions.waitForAffectedState(stateTransition), + ); } catch (affectedStateError) { throw toReportableError(affectedStateError); } diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index bf187692783..f001cac89a5 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -3,6 +3,9 @@ "private": true, "version": "5.0.0-beta.2", "description": "Dash Network end-to-end tests", + "engines": { + "node": ">=22" + }, "scripts": { "test": "yarn exec bin/test.sh", "lint": "eslint .", diff --git a/packages/platform-test-suite/test/unit/createPlatformProofVerifier.spec.js b/packages/platform-test-suite/test/unit/createPlatformProofVerifier.spec.js index 5794188d887..8c327746ce5 100644 --- a/packages/platform-test-suite/test/unit/createPlatformProofVerifier.spec.js +++ b/packages/platform-test-suite/test/unit/createPlatformProofVerifier.spec.js @@ -41,6 +41,99 @@ function executionNotProvedError() { } describe('createPlatformProofVerifier', () => { + describe('quorum publication delay', () => { + let clock; + + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); + + afterEach(() => { + clock.restore(); + }); + + function missingQuorumError() { + // Match the prototype getters and nested error reported by the WASM SDK in CI. + return Object.create({ + name: 'DapiClientError', + message: 'no available addresses to retry, last error: Proof verification error: ' + + 'context provider error: invalid quorum: Quorum not found in cache for hash: ' + + '0b32385bfc1e0147b7dee87de5db332105dfd603fef6070bd9c6323aa2493a46', + }); + } + + const input = { serializedStateTransition: Uint8Array.from([1, 2, 3]), network: 'local' }; + + it('should verify again after the sidecar publishes a rotated quorum', async () => { + const waitForResponse = sinon.stub().callsFake(() => { + if (waitForResponse.callCount === 1) { + return Promise.reject(missingQuorumError()); + } + if (Date.now() < 60000) { + return Promise.reject(new Error('no available addresses')); + } + return Promise.resolve(); + }); + const waitForAffectedState = sinon.stub().resolves(); + const { verifier, stateTransition } = createVerifierWith({ + waitForResponse, waitForAffectedState, + }); + const result = verifier.verifyStateTransitionResult(input).then(() => null, (error) => error); + + await clock.tickAsync(65000); + + expect(await result).to.equal(null); + expect(clock.now).to.equal(65000); + expect(waitForResponse.callCount).to.equal(2); + expect(waitForResponse).to.have.always.been.calledWithExactly(stateTransition); + expect(waitForAffectedState).to.not.have.been.called; + }); + + it('should fail after the bounded wait if the quorum never becomes available', async () => { + const waitForResponse = sinon.stub().rejects(missingQuorumError()); + const waitForAffectedState = sinon.stub().resolves(); + const { verifier } = createVerifierWith({ waitForResponse, waitForAffectedState }); + const result = verifier.verifyStateTransitionResult(input).then(() => null, (error) => error); + + await clock.tickAsync(65000); + + expect(await result).to.be.an.instanceOf(Error) + .with.property('message').that.includes('Quorum not found in cache'); + expect(waitForResponse.callCount).to.equal(2); + expect(waitForAffectedState).to.not.have.been.called; + expect(clock.countTimers()).to.equal(0); + }); + + it('should verify the affected-state proof again if its quorum is not published yet', async () => { + const waitForResponse = sinon.stub().rejects(executionNotProvedError()); + const waitForAffectedState = sinon.stub().resolves(); + waitForAffectedState.onFirstCall().rejects(missingQuorumError()); + const { verifier } = createVerifierWith({ waitForResponse, waitForAffectedState }); + const result = verifier.verifyStateTransitionResult(input).then(() => null, (error) => error); + + await clock.tickAsync(65000); + + expect(await result).to.equal(null); + expect(waitForAffectedState).to.have.been.calledTwice; + }); + + it('should stop immediately if the refreshed quorum reveals an invalid signature', async () => { + const proofError = new Error('quorum signature is invalid'); + const waitForResponse = sinon.stub().rejects(proofError); + waitForResponse.onFirstCall().rejects(missingQuorumError()); + const waitForAffectedState = sinon.stub().resolves(); + const { verifier } = createVerifierWith({ waitForResponse, waitForAffectedState }); + const result = verifier.verifyStateTransitionResult(input).then(() => null, (error) => error); + + await clock.tickAsync(65000); + + expect(await result).to.equal(proofError); + expect(waitForResponse).to.have.been.calledTwice; + expect(waitForAffectedState).to.not.have.been.called; + expect(clock.countTimers()).to.equal(0); + }); + }); + it('should require an execution proof before accepting a state transition', async () => { const waitForResponse = sinon.stub().resolves(); const waitForAffectedState = sinon.stub().resolves(); diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index dcda3d37fc9..bf8081e9518 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -12,6 +12,12 @@ authors = [ ] [dependencies] +dash-pkc = { git = "https://github.com/dashpay/base-sdk", rev = "e6402ced257c370a586ade9840ebbf545a4b7926", default-features = false, features = ["bls"], optional = true } +# Keep downstream lockfiles from retaining blst versions without WASM aggregate verification. +# TODO: Remove this constraint once the pinned rust-dashcore dependency graph enforces a compatible blst minimum. +blst = { version = "0.3.17", default-features = false, optional = true } +zeroize = { version = "1.9.0", optional = true } +ed25519-dalek = { version = "=2.2.0", features = ["rand_core"], optional = true } anyhow = { version = "1.0.81" } async-trait = { version = "0.1.79" } base64 = "0.22.1" @@ -90,6 +96,19 @@ once_cell = "1.7" env_logger = { version = "0.11.8" } log = { version = "0.4.27" } +# secp256k1 0.33 pulls getrandom 0.3 (via rand 0.9) and key-wallet pulls +# getrandom 0.4; on wasm32-unknown-unknown both refuse to build without their +# `wasm_js` feature, so enable it here where every wasm crate picks it up. +# Unlike getrandom 0.2's `js` (which falls back to Node's `require("crypto")`), +# `wasm_js` only reads `globalThis.crypto.getRandomValues`, which Node.js +# exposes by default from v19; the JS packages require Node >= 22. +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +getrandom_03 = { package = "getrandom", version = "0.3", features = ["wasm_js"] } +getrandom_04 = { package = "getrandom", version = "0.4", features = ["wasm_js"] } + +[package.metadata.cargo-machete] +ignored = ["getrandom_03", "getrandom_04", "blst"] + [features] default = ["state-transitions"] core_bincode = ["dashcore/bincode"] @@ -97,13 +116,12 @@ core_verification = ["dashcore/message_verification"] core_quorum_validation = ["dashcore/quorum_validation"] core_key_wallet = ["dep:key-wallet"] core_key_wallet_bincode = ["dep:key-wallet", "key-wallet/bincode"] -core_key_wallet_bip_38 = ["dep:key-wallet", "key-wallet/bip38"] core_key_wallet_manager = ["dep:key-wallet-manager"] core_key_wallet_serde = ["dep:key-wallet", "key-wallet/serde"] core_spv = ["dep:dash-spv"] core_rpc_client = ["dep:dashcore-rpc"] -bls-signatures = ["dashcore/bls"] -ed25519-dalek = ["dashcore/eddsa"] +bls-signatures = ["dashcore/bls", "dep:dash-pkc", "dep:blst", "dep:zeroize"] +ed25519-dalek = ["dashcore/eddsa", "dep:ed25519-dalek"] all_features = [ "json-conversion", "all-system_contracts", diff --git a/packages/rs-dpp/examples/generate_bls_compatibility_vectors.rs b/packages/rs-dpp/examples/generate_bls_compatibility_vectors.rs new file mode 100644 index 00000000000..f137030b63a --- /dev/null +++ b/packages/rs-dpp/examples/generate_bls_compatibility_vectors.rs @@ -0,0 +1,138 @@ +//! Emit deterministic compatibility fixtures from DPP's current BLS backend. +//! Run with `cargo run -p dpp --example generate_bls_compatibility_vectors`. + +use dpp::bls::{PublicKey, SecretKey, Signature}; +use dpp::core_types::validator::v0::ValidatorV0; +use dpp::core_types::validator_set::v0::ValidatorSetV0; +use dpp::dashcore::{hashes::Hash, ProTxHash, PubkeyHash, QuorumHash}; +use serde_json::json; +use std::collections::BTreeMap; + +fn main() { + let key_generation: Vec<_> = [[0u8; 32], [1; 32], [255; 32]].iter().map(|ikm| { + let key = SecretKey::from_ikm(ikm).unwrap(); + json!({"ikm": hex::encode(ikm), "secret_key": hex::encode(key.to_be_bytes()), "public_key": hex::encode(key.public_key().to_bytes())}) + }).collect(); + // Public test material: unit scalars, asymmetric bytes and the last canonical scalar. + let keys: Vec<[u8; 32]> = [ + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000", + ] + .iter() + .map(|s| hex::decode(s).unwrap().try_into().unwrap()) + .collect(); + let mut basic = Vec::new(); + for bytes in &keys { + let key = SecretKey::from_be_bytes(bytes).unwrap(); + for message in [ + vec![], + b"Dash Platform BLS compatibility".to_vec(), + vec![0; 32], + (0..=255).collect(), + ] { + let signature = key.sign(&message).unwrap(); + basic.push(json!({ + "secret_key": hex::encode(bytes), "message": hex::encode(message), + "public_key": hex::encode(key.public_key().to_bytes()), + "legacy_public_key": hex::encode(key.public_key().to_legacy_bytes().unwrap()), + "signature": hex::encode(signature.to_bytes()) + })); + } + } + let mut secure_aggregation = Vec::new(); + for indexes in [ + vec![0], + vec![0, 1], + vec![0, 1, 2], + vec![2, 0, 1], + vec![0, 0], + ] { + let message = b"Dash Platform secure aggregation"; + let signers: Vec<_> = indexes + .iter() + .map(|&i| SecretKey::from_be_bytes(&keys[i]).unwrap()) + .collect(); + let public_keys: Vec<_> = signers.iter().map(|key| key.public_key()).collect(); + let signatures: Vec<_> = signers + .iter() + .map(|key| key.sign(message).unwrap()) + .collect(); + let point = Signature::aggregate_secure(&signatures, &public_keys).unwrap(); + secure_aggregation.push(json!({ + "secret_keys": indexes.iter().map(|&i| hex::encode(keys[i])).collect::>(), + "message": hex::encode(message), + "public_keys": public_keys.iter().map(|key| hex::encode(key.to_bytes())).collect::>(), + "signature": hex::encode(point.to_bytes()) + })); + } + let mut scalars = Vec::new(); + for (name, bytes) in [ + ("zero", [0; 32]), + ( + "order", + hex::decode("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001") + .unwrap() + .try_into() + .unwrap(), + ), + ( + "order_plus_one", + hex::decode("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000002") + .unwrap() + .try_into() + .unwrap(), + ), + ("max", [255; 32]), + ] { + let key = SecretKey::from_be_bytes(&bytes); + scalars.push(json!({ + "name": name, "input": hex::encode(bytes), + "normalized": key.as_ref().map(|key| hex::encode(key.to_be_bytes())), + "public_key": key.as_ref().map(|key| hex::encode(key.public_key().to_bytes())), + "signature": key.as_ref().map(|key| hex::encode(key.sign(b"scalar boundary").unwrap().to_bytes())) + })); + } + let mut infinity = [0; 48]; + infinity[0] = 0xc0; + let public_key = SecretKey::from_be_bytes(&keys[0]).unwrap().public_key(); + let mut storage = Vec::new(); + for (name, key) in [ + ("public_key", Some(public_key)), + ("absent", None), + ( + "infinity", + Some(PublicKey::try_from(infinity.as_slice()).unwrap()), + ), + ] { + let validator = ValidatorV0 { + pro_tx_hash: ProTxHash::from_byte_array([0x11; 32]), + public_key: key, + node_ip: "127.0.0.1".into(), + node_id: PubkeyHash::from_byte_array([0x22; 20]), + core_port: 19999, + platform_http_port: 1443, + platform_p2p_port: 26656, + is_banned: false, + }; + let config = bincode::config::standard().with_big_endian(); + let set = ValidatorSetV0 { + quorum_hash: QuorumHash::from_byte_array([0x33; 32]), + quorum_index: Some(2), + core_height: 123456, + members: BTreeMap::from([(validator.pro_tx_hash, validator.clone())]), + threshold_public_key: public_key, + }; + storage.push(json!({ + "name": name, "validator": serde_json::to_value(&validator).unwrap(), + "validator_bytes": hex::encode(bincode::encode_to_vec(&validator, config).unwrap()), + "validator_set": serde_json::to_value(&set).unwrap(), + "validator_set_bytes": hex::encode(bincode::encode_to_vec(&set, config).unwrap()) + })); + } + println!("{}", serde_json::to_string_pretty(&json!({ + "basic": basic, "secure_aggregation": secure_aggregation, "scalars": scalars, "storage": storage, + "key_generation": key_generation + })).unwrap()); +} diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 3d8c9dd7be0..bedf9343b5c 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -11,7 +11,6 @@ use bincode::{Decode, DecodeUntrusted, Encode}; use dashcore::address::Payload; use dashcore::blockdata::script::ScriptBuf; use dashcore::hashes::{sha256d, Hash}; -use dashcore::key::Secp256k1; use dashcore::secp256k1::ecdsa::RecoverableSignature; use dashcore::secp256k1::Message; use dashcore::signer::CompactSignature; @@ -247,8 +246,7 @@ impl From<&PrivateKey> for PlatformAddress { /// The address is derived as: P2PKH(Hash160(compressed_public_key)) /// where Hash160 = RIPEMD160(SHA256(x)), which is the standard Bitcoin P2PKH derivation. fn from(private_key: &PrivateKey) -> Self { - let secp = Secp256k1::new(); - let pubkey_hash = private_key.public_key(&secp).pubkey_hash(); + let pubkey_hash = private_key.public_key().pubkey_hash(); PlatformAddress::P2pkh(*pubkey_hash.as_byte_array()) } } @@ -555,7 +553,6 @@ impl PlatformAddress { let signable_bytes_hash = sha256d::Hash::hash(signable_bytes).to_byte_array(); let msg = Message::from_digest(signable_bytes_hash); - let secp = Secp256k1::new(); while sig_idx < valid_signatures.len() && pubkey_idx < pubkeys.len() { signature_verifications += 1; @@ -574,10 +571,7 @@ impl PlatformAddress { ProtocolError::AddressWitnessError(format!("Invalid public key: {}", e)) })?; - if secp - .verify_ecdsa(&msg, &sig.to_standard(), &pub_key.inner) - .is_ok() - { + if sig.to_standard().verify(msg, &pub_key.inner).is_ok() { matched += 1; sig_idx += 1; } @@ -798,7 +792,7 @@ mod tests { use super::*; use dashcore::blockdata::opcodes::all::*; use dashcore::hashes::Hash; - use dashcore::secp256k1::{PublicKey as RawPublicKey, Secp256k1, SecretKey as RawSecretKey}; + use dashcore::secp256k1::{PublicKey as RawPublicKey, SecretKey as RawSecretKey}; use dashcore::PublicKey; use platform_value::BinaryData; @@ -822,16 +816,15 @@ mod tests { /// Helper to create a keypair from a 32-byte seed fn create_keypair(seed: [u8; 32]) -> (RawSecretKey, PublicKey) { - let secp = Secp256k1::new(); - let secret_key = RawSecretKey::from_byte_array(&seed).expect("valid secret key"); - let raw_public_key = RawPublicKey::from_secret_key(&secp, &secret_key); + let secret_key = RawSecretKey::from_secret_bytes(seed).expect("valid secret key"); + let raw_public_key = RawPublicKey::from_secret_key(&secret_key); let public_key = PublicKey::new(raw_public_key); (secret_key, public_key) } /// Helper to sign data with a secret key fn sign_data(data: &[u8], secret_key: &RawSecretKey) -> Vec { - dashcore::signer::sign(data, secret_key.as_ref()) + dashcore::signer::sign(data, secret_key.as_secret_bytes()) .expect("signing should succeed") .to_vec() } diff --git a/packages/rs-dpp/src/bls/bls_signatures.rs b/packages/rs-dpp/src/bls/bls_signatures.rs new file mode 100644 index 00000000000..a3f9d3bb2b6 --- /dev/null +++ b/packages/rs-dpp/src/bls/bls_signatures.rs @@ -0,0 +1,236 @@ +//! Platform BLS keys and Basic signatures backed by rust-dashcore's dash-pkc primitives. + +use dash_pkc::bls::{BlsPublicKey, BlsScChia, BlsScIetf, BlsSecretKey, BlsSignature, Fr}; +use rand::{CryptoRng, Rng, RngCore}; +use std::fmt; +use zeroize::Zeroizing; + +const fn infinity() -> [u8; N] { + let mut bytes = [0; N]; + bytes[0] = 0xc0; + bytes +} + +/// Errors exposed by Platform's BLS operations. +#[derive(Debug, Clone, thiserror::Error)] +pub enum BlsError { + /// An input has an invalid length or encoding. + #[error("invalid inputs: {0}")] + InvalidInputs(String), + /// The signature does not verify. + #[error("invalid signature")] + InvalidSignature, + /// A key or signature could not be decoded. + #[error("deserialization error: {0}")] + DeserializationError(String), +} + +/// A validated G1 key, including the identity permitted by historical Platform parsing. +#[derive(Clone, Copy, Default, Eq, PartialEq)] +pub struct PublicKey(PublicKeyPoint); + +#[derive(Clone, Copy, Default, Eq, PartialEq)] +enum PublicKeyPoint { + #[default] + Infinity, + Validated(BlsPublicKey), +} + +impl PublicKey { + /// Return the canonical compressed IETF encoding. + pub fn to_bytes(&self) -> [u8; 48] { + match &self.0 { + PublicKeyPoint::Infinity => infinity(), + PublicKeyPoint::Validated(key) => key.to_bytes(), + } + } + + /// Re-encode a public key for Core's legacy wire format. + pub fn to_legacy_bytes(&self) -> Result<[u8; 48], BlsError> { + let PublicKeyPoint::Validated(key) = &self.0 else { + return Ok(infinity()); + }; + key.to_scheme::() + .map(|key| key.to_bytes()) + .map_err(|_| BlsError::InvalidInputs("Invalid byte sequence".into())) + } + + fn validated(&self) -> Result<&BlsPublicKey, BlsError> { + match &self.0 { + PublicKeyPoint::Infinity => Err(BlsError::InvalidSignature), + PublicKeyPoint::Validated(key) => Ok(key), + } + } +} + +impl From<&SecretKey> for PublicKey { + fn from(key: &SecretKey) -> Self { + key.public_key() + } +} + +impl TryFrom<&[u8]> for PublicKey { + type Error = BlsError; + + fn try_from(value: &[u8]) -> Result { + let bytes = value.try_into().map_err(|_| { + BlsError::InvalidInputs(format!("Invalid length, expected 48, got {}", value.len())) + })?; + // Persisted state and shipped validation accept canonical infinity, but verification rejects it. + if bytes == infinity() { + return Ok(Self(PublicKeyPoint::Infinity)); + } + BlsPublicKey::from_bytes(&bytes) + .map(|key| Self(PublicKeyPoint::Validated(key))) + .map_err(|_| BlsError::InvalidInputs("Invalid byte sequence".into())) + } +} + +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(self.to_bytes())) + } +} + +impl fmt::Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +/// A secret scalar; the backend clears its storage on drop. +#[derive(Clone, Debug)] +pub struct SecretKey(BlsSecretKey); + +impl SecretKey { + /// Parse a big-endian scalar using Platform's historical nonzero reduction rule. + pub fn from_be_bytes(bytes: &[u8; 32]) -> Option { + let scalar = Zeroizing::new(Fr::from_bendian_reduce(bytes).ok()?); + BlsSecretKey::try_from(*scalar).ok().map(Self) + } + + /// Derive a key from at least 32 bytes of keying material using Core's keygen-v3. + pub fn from_ikm(ikm: &[u8]) -> Result { + BlsSecretKey::from_ikm(ikm) + .map(Self) + .map_err(|e| BlsError::InvalidInputs(e.to_string())) + } + + /// Draw keying material with Platform's rand interface and derive a secret key. + pub fn random(mut rng: impl RngCore + CryptoRng) -> Self { + loop { + let material = Zeroizing::new(rng.gen::<[u8; 32]>()); + if let Ok(key) = Self::from_ikm(material.as_ref()) { + return key; + } + } + } + + /// Return the canonical big-endian secret scalar. + pub fn to_be_bytes(&self) -> [u8; 32] { + *self.0.to_bytes() + } + + /// Derive the corresponding public key. + pub fn public_key(&self) -> PublicKey { + PublicKey(PublicKeyPoint::Validated(self.0.public_key())) + } + + /// Sign arbitrary message bytes with the Basic IETF domain separation tag. + pub fn sign(&self, message: &[u8]) -> Result { + Ok(Signature::from_validated(self.0.sign(message))) + } +} + +/// A validated G2 Basic signature, with canonical infinity represented separately. +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Signature(Option>); + +impl fmt::Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Signature").field(&self.to_bytes()).finish() + } +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&hex::encode(self.to_bytes())) + } +} + +impl Signature { + /// Decode a subgroup point, including the identity rejected during verification. + pub fn from_compressed(bytes: &[u8; 96]) -> Option { + if *bytes == infinity() { + return Some(Self(None)); + } + BlsSignature::from_bytes(bytes) + .ok() + .map(|signature| Self(Some(signature))) + } + + fn from_validated(signature: BlsSignature) -> Self { + // Aggregation can produce infinity; keep its historical parsing and verification policy. + Self((signature.to_bytes() != infinity()).then_some(signature)) + } + + /// Return the compressed IETF encoding without a scheme tag. + pub fn to_bytes(&self) -> [u8; 96] { + self.0 + .as_ref() + .map_or_else(infinity, BlsSignature::to_bytes) + } + + /// Verify a Basic signature over arbitrary message bytes. + pub fn verify( + &self, + public_key: &PublicKey, + message: impl AsRef<[u8]>, + ) -> Result<(), BlsError> { + let signature = self + .0 + .as_ref() + .ok_or_else(|| BlsError::InvalidInputs("signature is the identity point".into()))?; + let PublicKeyPoint::Validated(key) = &public_key.0 else { + return Err(BlsError::InvalidInputs( + "public key is the identity point".into(), + )); + }; + key.verify(message.as_ref(), signature) + .map_err(|_| BlsError::InvalidSignature) + } + + /// Aggregate signatures with deterministic public-key weights. + pub fn aggregate_secure( + signatures: &[Self], + public_keys: &[PublicKey], + ) -> Result { + let signatures: Vec<_> = signatures + .iter() + .map(|sig| sig.0.as_ref().ok_or(BlsError::InvalidSignature)) + .collect::>()?; + let keys: Vec<_> = public_keys + .iter() + .map(PublicKey::validated) + .collect::>()?; + BlsSignature::secure_aggregate(&signatures, &keys) + .map(Self::from_validated) + .map_err(|_| BlsError::InvalidSignature) + } + + /// Verify a secure aggregate over a common message. + pub fn verify_secure( + &self, + public_keys: &[PublicKey], + message: impl AsRef<[u8]>, + ) -> Result<(), BlsError> { + let keys: Vec<_> = public_keys + .iter() + .map(PublicKey::validated) + .collect::>()?; + let signature = self.0.as_ref().ok_or(BlsError::InvalidSignature)?; + signature + .secure_verify_aggregates(message.as_ref(), &keys) + .map_err(|_| BlsError::InvalidSignature) + } +} diff --git a/packages/rs-dpp/src/bls/mod.rs b/packages/rs-dpp/src/bls/mod.rs index a5501f4634d..44b95b4a2bb 100644 --- a/packages/rs-dpp/src/bls/mod.rs +++ b/packages/rs-dpp/src/bls/mod.rs @@ -1,5 +1,12 @@ #[cfg(feature = "bls-signatures")] +mod bls_signatures; +#[cfg(feature = "bls-signatures")] +pub use bls_signatures::{BlsError, PublicKey, SecretKey, Signature}; +#[cfg(feature = "bls-signatures")] pub mod native_bls; +/// Serde implementations and field adapters for BLS public keys. +#[cfg(feature = "bls-signatures")] +pub mod serde; use crate::{ProtocolError, PublicKeyValidationError}; diff --git a/packages/rs-dpp/src/bls/native_bls.rs b/packages/rs-dpp/src/bls/native_bls.rs index 3e1341bf9ab..2bcd1dd4273 100644 --- a/packages/rs-dpp/src/bls/native_bls.rs +++ b/packages/rs-dpp/src/bls/native_bls.rs @@ -1,13 +1,11 @@ -use crate::bls_signatures::{ - Bls12381G2Impl, Pairing, PublicKey, SecretKey, Signature, SignatureSchemes, -}; +use crate::bls::{PublicKey, SecretKey, Signature}; use crate::{BlsModule, ProtocolError, PublicKeyValidationError}; #[derive(Default)] pub struct NativeBlsModule; impl BlsModule for NativeBlsModule { fn validate_public_key(&self, pk: &[u8]) -> Result<(), PublicKeyValidationError> { - match PublicKey::::try_from(pk) { + match PublicKey::try_from(pk) { Ok(_) => Ok(()), Err(e) => Err(PublicKeyValidationError::new(e.to_string())), } @@ -19,22 +17,17 @@ impl BlsModule for NativeBlsModule { data: &[u8], public_key: &[u8], ) -> Result { - let public_key = PublicKey::::try_from(public_key)?; + let public_key = PublicKey::try_from(public_key)?; let signature_96_bytes = signature .try_into() .map_err(|_| ProtocolError::BlsSignatureSizeError { got: signature.len() as u32, })?; - let Some(g2_element) = - ::Signature::from_compressed(&signature_96_bytes) - .into_option() - else { + let Some(signature) = Signature::from_compressed(&signature_96_bytes) else { return Ok(false); // We should not error because the signature could be given by an invalid source }; - let signature = Signature::Basic(g2_element); - match signature.verify(&public_key, data) { Ok(_) => Ok(true), Err(_) => Ok(false), @@ -48,13 +41,11 @@ impl BlsModule for NativeBlsModule { .map_err(|_| ProtocolError::PrivateKeySizeError { got: private_key.len() as u32, })?; - let pk = SecretKey::::from_be_bytes(&fixed_len_key) - .into_option() - .ok_or(ProtocolError::InvalidBLSPrivateKeyError( - "key not valid".to_string(), - ))?; + let pk = SecretKey::from_be_bytes(&fixed_len_key).ok_or( + ProtocolError::InvalidBLSPrivateKeyError("key not valid".to_string()), + )?; let public_key = pk.public_key(); - let public_key_bytes = public_key.0.to_compressed().to_vec(); + let public_key_bytes = public_key.to_bytes().to_vec(); Ok(public_key_bytes) } @@ -65,15 +56,9 @@ impl BlsModule for NativeBlsModule { .map_err(|_| ProtocolError::PrivateKeySizeError { got: private_key.len() as u32, })?; - let pk = SecretKey::::from_be_bytes(&fixed_len_key) - .into_option() - .ok_or(ProtocolError::InvalidBLSPrivateKeyError( - "key not valid".to_string(), - ))?; - Ok(pk - .sign(SignatureSchemes::Basic, data)? - .as_raw_value() - .to_compressed() - .to_vec()) + let pk = SecretKey::from_be_bytes(&fixed_len_key).ok_or( + ProtocolError::InvalidBLSPrivateKeyError("key not valid".to_string()), + )?; + Ok(pk.sign(data)?.to_bytes().to_vec()) } } diff --git a/packages/rs-dpp/src/bls/serde.rs b/packages/rs-dpp/src/bls/serde.rs new file mode 100644 index 00000000000..205a1e71b8a --- /dev/null +++ b/packages/rs-dpp/src/bls/serde.rs @@ -0,0 +1,281 @@ +//! Serde support for Platform BLS keys in hex, byte-sequence and tagged-enum inputs. + +use crate::bls::PublicKey as BlsPublicKey; +use ::serde::de::Visitor; +use ::serde::ser::SerializeTuple; +use ::serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; + +/// Size of a compressed BLS12-381 G1 public key. +const COMPRESSED_G1_LEN: usize = 48; + +impl Serialize for BlsPublicKey { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + serializer.serialize_str(&hex::encode(self.to_bytes())) + } else { + let mut tuple = serializer.serialize_tuple(COMPRESSED_G1_LEN)?; + for byte in self.to_bytes() { + tuple.serialize_element(&byte)?; + } + tuple.end() + } + } +} + +impl<'de> Deserialize<'de> for BlsPublicKey { + fn deserialize>(deserializer: D) -> Result { + if deserializer.is_human_readable() { + deserializer.deserialize_any(BlsPublicKeyVisitor) + } else { + // Bincode needs the fixed tuple length and cannot use deserialize_any. + deserializer.deserialize_tuple(COMPRESSED_G1_LEN, BlsPublicKeyVisitor) + } + } +} + +/// Serialize a public-key field with the DPP key's standard representation. +pub fn serialize(pk: &BlsPublicKey, serializer: S) -> Result { + pk.serialize(serializer) +} + +/// Decode a field from self-describing or buffered tagged-enum data. +/// +/// For formats such as bincode, use the key's `Deserialize` implementation instead. +pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + // Tagged enum buffers may supply either representation regardless of is_human_readable(). + deserializer.deserialize_any(BlsPublicKeyVisitor) +} + +fn from_compressed_g1_bytes(bytes: &[u8]) -> Result { + if bytes.len() != COMPRESSED_G1_LEN { + return Err(E::custom(format!( + "expected {COMPRESSED_G1_LEN} compressed-G1 bytes for public key, got {}", + bytes.len() + ))); + } + BlsPublicKey::try_from(bytes).map_err(|_| E::custom("not a valid compressed G1 point")) +} + +struct BlsPublicKeyVisitor; + +impl<'de> Visitor<'de> for BlsPublicKeyVisitor { + type Value = BlsPublicKey; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "a {}-char hex string or {} compressed-G1 bytes", + COMPRESSED_G1_LEN * 2, + COMPRESSED_G1_LEN + ) + } + + fn visit_str(self, s: &str) -> Result { + if s.len() != COMPRESSED_G1_LEN * 2 { + return Err(E::custom(format!( + "expected {} hex chars for compressed G1 public key, got {}", + COMPRESSED_G1_LEN * 2, + s.len() + ))); + } + let mut bytes = [0u8; COMPRESSED_G1_LEN]; + for (i, slot) in bytes.iter_mut().enumerate() { + let hi = hex_nibble(s.as_bytes()[i * 2]).map_err(E::custom)?; + let lo = hex_nibble(s.as_bytes()[i * 2 + 1]).map_err(E::custom)?; + *slot = (hi << 4) | lo; + } + from_compressed_g1_bytes(&bytes) + } + + fn visit_bytes(self, v: &[u8]) -> Result { + from_compressed_g1_bytes(v) + } + + fn visit_seq>( + self, + mut seq: A, + ) -> Result { + let mut bytes = Vec::with_capacity(COMPRESSED_G1_LEN); + while let Some(b) = seq.next_element::()? { + // A valid compressed-G1 public key is exactly COMPRESSED_G1_LEN + // bytes; reject as soon as a hostile payload exceeds that rather + // than allocating/parsing an arbitrarily long sequence first. + if bytes.len() == COMPRESSED_G1_LEN { + return Err(::serde::de::Error::invalid_length(bytes.len() + 1, &self)); + } + bytes.push(b); + } + from_compressed_g1_bytes(&bytes) + } +} + +fn hex_nibble(c: u8) -> Result { + match c { + b'0'..=b'9' => Ok(c - b'0'), + b'a'..=b'f' => Ok(c - b'a' + 10), + b'A'..=b'F' => Ok(c - b'A' + 10), + _ => Err("invalid hex character in compressed G1 public key"), + } +} + +/// `Option` variant for fields like +/// `Validator::public_key`. +pub mod option { + use super::*; + + pub fn serialize( + opt: &Option, + serializer: S, + ) -> Result { + // Option delegates to the DPP public key's Serialize implementation. + opt.serialize(serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + struct OptionVisitor; + + impl<'de> Visitor<'de> for OptionVisitor { + type Value = Option; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Option") + } + + fn visit_none(self) -> Result { + Ok(None) + } + + fn visit_unit(self) -> Result { + Ok(None) + } + + fn visit_some>( + self, + inner: D2, + ) -> Result { + super::deserialize(inner).map(Some) + } + } + + deserializer.deserialize_option(OptionVisitor) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ::serde::{Deserialize, Serialize}; + use serde_json::json; + + // A known-valid compressed-G1 BLS public key (deterministic — the + // ValidatorSet fixture's seeded StdRng(42) threshold key). + const PK_HEX: &str = + "969c5d5873f49aa994c5f6a850924ca1840c4ad1791aaaecd90093d4a5c0c3799f2d98540f5366cfa0a33f143fd69263"; + + // Newtypes that drive the `with` module(s) through serde. + #[derive(Serialize, Deserialize)] + struct Wrap(#[serde(with = "super")] BlsPublicKey); + + #[derive(Serialize, Deserialize)] + struct OptWrap(#[serde(with = "super::option")] Option); + + #[test] + fn should_accept_the_same_json_representations_directly_and_through_the_adapter() { + let bytes = hex::decode(PK_HEX).unwrap(); + for value in [json!(PK_HEX), json!(PK_HEX.to_uppercase()), json!(bytes)] { + let direct: BlsPublicKey = serde_json::from_value(value.clone()).unwrap(); + let adapted: Wrap = serde_json::from_value(value).unwrap(); + assert_eq!(direct, adapted.0); + assert_eq!(direct.to_bytes().as_slice(), bytes); + } + } + + #[test] + fn should_reject_malformed_keys_directly_and_through_the_adapter() { + for value in [ + json!("ab"), + json!("z".repeat(96)), + json!(vec![0u8; 47]), + json!(vec![0u8; 49]), + json!(vec![0u8; 48]), + ] { + assert!(serde_json::from_value::(value.clone()).is_err()); + assert!(serde_json::from_value::(value).is_err()); + } + } + + #[test] + fn should_decode_bincode_keys_without_consuming_the_following_fields() { + let key: BlsPublicKey = serde_json::from_value(json!(PK_HEX)).unwrap(); + let record = (key, 0x21u8, Some(key)); + let config = bincode::config::standard().with_big_endian(); + let encoded = bincode::serde::encode_to_vec(record, config).unwrap(); + let mut expected = key.to_bytes().to_vec(); + expected.extend([0x21, 1]); + expected.extend(key.to_bytes()); + assert_eq!(encoded, expected); + let (decoded, consumed): ((BlsPublicKey, u8, Option), usize) = + bincode::serde::decode_from_slice(&encoded, config).unwrap(); + assert_eq!(decoded, record); + assert_eq!(consumed, encoded.len()); + assert!( + bincode::serde::decode_from_slice::(&encoded[..47], config).is_err() + ); + } + + #[test] + fn should_keep_legacy_field_adapter_paths_working() { + #[derive(Serialize, Deserialize)] + struct LegacyKey( + #[serde(with = "crate::serialization::dashcore::bls_pubkey")] BlsPublicKey, + ); + #[derive(Serialize, Deserialize)] + struct LegacyOption( + #[serde(with = "crate::serialization::dashcore::bls_pubkey::option")] + Option, + ); + + let key: LegacyKey = serde_json::from_value(json!(PK_HEX)).unwrap(); + assert_eq!(serde_json::to_value(key).unwrap(), json!(PK_HEX)); + for value in [json!(PK_HEX), json!(null)] { + let key: LegacyOption = serde_json::from_value(value.clone()).unwrap(); + assert_eq!(serde_json::to_value(key).unwrap(), value); + } + } + + #[test] + fn json_hr_round_trip_is_the_hex_string() { + // Human-readable (serde_json::Value): hex in, identical hex out (visit_str). + let pk: Wrap = serde_json::from_value(json!(PK_HEX)).expect("from hex"); + assert_eq!(serde_json::to_value(&pk).expect("to json"), json!(PK_HEX)); + } + + #[test] + fn json_borrowed_str_path_works() { + // The shared visitor accepts borrowed strings as well as owned JSON values. + let pk: Wrap = serde_json::from_str(&format!("\"{PK_HEX}\"")).expect("from_str"); + assert_eq!(serde_json::to_value(&pk).expect("to json"), json!(PK_HEX)); + } + + #[test] + fn value_non_hr_byte_seq_round_trip() { + // Non-HR `platform_value` serializes the key as a 48-byte sequence; the + // shared visit_seq path must reconstruct it. Re-serialize to JSON to + // confirm the same key. + let pk: Wrap = serde_json::from_value(json!(PK_HEX)).expect("from hex"); + let value = platform_value::to_value(&pk).expect("to value"); + let pk2: Wrap = platform_value::from_value(value).expect("from value seq"); + assert_eq!(serde_json::to_value(&pk2).expect("to json"), json!(PK_HEX)); + } + + #[test] + fn option_some_and_none_round_trip() { + let some: OptWrap = serde_json::from_value(json!(PK_HEX)).expect("some"); + assert_eq!(serde_json::to_value(&some).expect("to json"), json!(PK_HEX)); + let none: OptWrap = serde_json::from_value(json!(null)).expect("none"); + assert_eq!(serde_json::to_value(&none).expect("to json"), json!(null)); + } +} diff --git a/packages/rs-dpp/src/core_types/validator/mod.rs b/packages/rs-dpp/src/core_types/validator/mod.rs index 5cc5681b205..7215cddba58 100644 --- a/packages/rs-dpp/src/core_types/validator/mod.rs +++ b/packages/rs-dpp/src/core_types/validator/mod.rs @@ -1,4 +1,4 @@ -use crate::bls_signatures::{Bls12381G2Impl, PublicKey as BlsPublicKey}; +use crate::bls::PublicKey as BlsPublicKey; use crate::core_types::validator::v0::{ValidatorV0, ValidatorV0Getters, ValidatorV0Setters}; #[cfg(all(feature = "json-conversion", feature = "serde-conversion"))] use crate::serialization::JsonConvertible; @@ -37,7 +37,7 @@ impl ValidatorV0Getters for Validator { } } - fn public_key(&self) -> &Option> { + fn public_key(&self) -> &Option { match self { Validator::V0(v0) => v0.public_key(), } @@ -87,7 +87,7 @@ impl ValidatorV0Setters for Validator { } } - fn set_public_key(&mut self, public_key: Option>) { + fn set_public_key(&mut self, public_key: Option) { match self { Validator::V0(v0) => v0.set_public_key(public_key), } diff --git a/packages/rs-dpp/src/core_types/validator/v0/mod.rs b/packages/rs-dpp/src/core_types/validator/v0/mod.rs index e99533cc51e..eedbcb3dd1b 100644 --- a/packages/rs-dpp/src/core_types/validator/v0/mod.rs +++ b/packages/rs-dpp/src/core_types/validator/v0/mod.rs @@ -1,7 +1,7 @@ use dashcore::{ProTxHash, PubkeyHash}; use std::fmt::{Debug, Formatter}; -use crate::bls_signatures::{Bls12381G2Impl, PublicKey as BlsPublicKey}; +use crate::bls::PublicKey as BlsPublicKey; #[cfg(feature = "serde-conversion")] use serde::{Deserialize, Serialize}; @@ -21,14 +21,12 @@ pub struct ValidatorV0 { /// The proTxHash pub pro_tx_hash: ProTxHash, /// The public key share of this validator for this quorum - // `BlsPublicKey` is a dashcore type, so its serde wrapper lives in - // `serialization::dashcore::bls_pubkey` (now self-sufficient — no upstream - // dependency; accepts hex string or byte sequence through any deserializer). + // Tagged enum buffers can carry either hex strings or byte sequences. #[cfg_attr( feature = "serde-conversion", - serde(with = "crate::serialization::dashcore::bls_pubkey::option") + serde(with = "crate::bls::serde::option") )] - pub public_key: Option>, + pub public_key: Option, /// The node address pub node_ip: String, /// The node id @@ -56,7 +54,7 @@ impl Encode for ValidatorV0 { match &self.public_key { Some(public_key) => { true.encode(encoder)?; // Indicate that public_key is present - public_key.0.to_compressed().encode(encoder)?; + public_key.to_bytes().encode(encoder)?; } None => { false.encode(encoder)?; // Indicate that public_key is not present @@ -168,7 +166,7 @@ pub trait ValidatorV0Getters { /// Returns the proTxHash of the validator. fn pro_tx_hash(&self) -> &ProTxHash; /// Returns the public key share of this validator for this quorum. - fn public_key(&self) -> &Option>; + fn public_key(&self) -> &Option; /// Returns the node address of the validator. fn node_ip(&self) -> &String; /// Returns the node id of the validator. @@ -188,7 +186,7 @@ pub trait ValidatorV0Setters { /// Sets the proTxHash of the validator. fn set_pro_tx_hash(&mut self, pro_tx_hash: ProTxHash); /// Sets the public key share of this validator for this quorum. - fn set_public_key(&mut self, public_key: Option>); + fn set_public_key(&mut self, public_key: Option); /// Sets the node address of the validator. fn set_node_ip(&mut self, node_ip: String); /// Sets the node id of the validator. @@ -208,7 +206,7 @@ impl ValidatorV0Getters for ValidatorV0 { &self.pro_tx_hash } - fn public_key(&self) -> &Option> { + fn public_key(&self) -> &Option { &self.public_key } @@ -242,7 +240,7 @@ impl ValidatorV0Setters for ValidatorV0 { self.pro_tx_hash = pro_tx_hash; } - fn set_public_key(&mut self, public_key: Option>) { + fn set_public_key(&mut self, public_key: Option) { self.public_key = public_key; } @@ -274,8 +272,8 @@ impl ValidatorV0Setters for ValidatorV0 { #[cfg(test)] mod tests { use super::*; + use crate::bls::SecretKey; use bincode::config; - use dashcore::blsful::SecretKey; use rand::prelude::StdRng; use rand::SeedableRng; @@ -284,7 +282,7 @@ mod tests { // Sample data for testing let pro_tx_hash = ProTxHash::from_slice(&[1; 32]).unwrap(); let mut rng = StdRng::seed_from_u64(0); - let public_key = Some(SecretKey::::random(&mut rng).public_key()); + let public_key = Some(SecretKey::random(&mut rng).public_key()); let node_ip = "127.0.0.1".to_string(); let node_id = PubkeyHash::from_slice(&[3; 20]).unwrap(); let core_port = 9999; diff --git a/packages/rs-dpp/src/core_types/validator_set/mod.rs b/packages/rs-dpp/src/core_types/validator_set/mod.rs index 13c934cdf2f..0b517f4ed00 100644 --- a/packages/rs-dpp/src/core_types/validator_set/mod.rs +++ b/packages/rs-dpp/src/core_types/validator_set/mod.rs @@ -1,4 +1,4 @@ -use crate::bls_signatures::{Bls12381G2Impl, PublicKey as BlsPublicKey}; +use crate::bls::PublicKey as BlsPublicKey; use crate::core_types::validator::v0::ValidatorV0; use crate::core_types::validator_set::v0::{ ValidatorSetV0, ValidatorSetV0Getters, ValidatorSetV0Setters, @@ -101,7 +101,7 @@ impl ValidatorSetV0Getters for ValidatorSet { } } - fn threshold_public_key(&self) -> &BlsPublicKey { + fn threshold_public_key(&self) -> &BlsPublicKey { match self { ValidatorSet::V0(v0) => v0.threshold_public_key(), } @@ -133,7 +133,7 @@ impl ValidatorSetV0Setters for ValidatorSet { } } - fn set_threshold_public_key(&mut self, threshold_public_key: BlsPublicKey) { + fn set_threshold_public_key(&mut self, threshold_public_key: BlsPublicKey) { match self { ValidatorSet::V0(v0) => v0.set_threshold_public_key(threshold_public_key), } @@ -148,9 +148,9 @@ impl ValidatorSetV0Setters for ValidatorSet { ))] mod json_convertible_tests { use super::*; + use crate::bls::SecretKey; use crate::core_types::validator::v0::ValidatorV0; use crate::core_types::validator_set::v0::ValidatorSetV0; - use dashcore::blsful::{Bls12381G2Impl, SecretKey}; use dashcore::hashes::Hash; use dashcore::{ProTxHash, PubkeyHash, QuorumHash}; use platform_value::{platform_value, Value}; @@ -165,16 +165,12 @@ mod json_convertible_tests { /// keys ARE deterministic, but the 96-char hex / 48-byte literal is too /// unwieldy to inline as a string constant, so we interpolate the actual /// `to_value`/`to_json` of the same pubkey objects we put in the fixture. - /// (The dedicated `serialization::dashcore::bls_pubkey` unit tests + /// (The dedicated `bls::serde` unit tests /// independently cover the pubkey round-trip.) - fn build_fixture() -> ( - ValidatorSet, - BlsPublicKey, - BlsPublicKey, - ) { + fn build_fixture() -> (ValidatorSet, BlsPublicKey, BlsPublicKey) { let mut rng = StdRng::seed_from_u64(42); let pro_tx_hash = ProTxHash::from_byte_array([0x11; 32]); - let validator_pubkey = SecretKey::::random(&mut rng).public_key(); + let validator_pubkey = SecretKey::random(&mut rng).public_key(); let validator_v0 = ValidatorV0 { pro_tx_hash, public_key: Some(validator_pubkey), @@ -188,7 +184,7 @@ mod json_convertible_tests { let mut members = BTreeMap::new(); members.insert(pro_tx_hash, validator_v0); - let threshold_pubkey = SecretKey::::random(&mut rng).public_key(); + let threshold_pubkey = SecretKey::random(&mut rng).public_key(); let set = ValidatorSet::V0(ValidatorSetV0 { quorum_hash: QuorumHash::from_byte_array([0x33; 32]), quorum_index: Some(7), @@ -204,7 +200,7 @@ mod json_convertible_tests { // visible and verifiable in-place — neither path interpolates `to_json` / // `to_value` of the object under test. JSON (HR) uses the hex string // directly; the non-HR `Value` form is the *same* 48 bytes decoded into an - // `Array` of `U8` (blstrs serializes the pubkey through a `u8` tuple, which + // `Array` of `U8` (the DPP key serializes through a `u8` tuple, which // platform_value collects as `Array[U8]` — NOT a typed `Bytes` variant). const VALIDATOR_PK_HEX: &str = "85d81dd12c73cca83f7d1bf8b78fadb695e3a2bc21d53b35ff2f74eaa28c6e163c98d3d5f9bb7252b4d836e484c7cc60"; @@ -234,7 +230,7 @@ mod json_convertible_tests { // keyed by ProTxHash hex, hash fields as lowercase hex strings, // sized-int fields preserved. The inner Validator's own // `$formatVersion` tag (now applied) appears alongside its other - // snake_case fields. (`serialization::dashcore::bls_pubkey` additionally + // snake_case fields. (`bls::serde` additionally // has its own dedicated BLS round-trip tests.) assert_eq!( json, diff --git a/packages/rs-dpp/src/core_types/validator_set/v0/mod.rs b/packages/rs-dpp/src/core_types/validator_set/v0/mod.rs index 45a19e2224c..b988a0a1f43 100644 --- a/packages/rs-dpp/src/core_types/validator_set/v0/mod.rs +++ b/packages/rs-dpp/src/core_types/validator_set/v0/mod.rs @@ -1,4 +1,4 @@ -use crate::bls_signatures::PublicKey as BlsPublicKey; +use crate::bls::PublicKey as BlsPublicKey; use crate::core_types::validator::v0::ValidatorV0; #[cfg(feature = "core-types-serialization")] use bincode::de::BorrowDecoder; @@ -8,7 +8,6 @@ use bincode::enc::Encoder; use bincode::error::EncodeError; #[cfg(feature = "core-types-serialization")] use bincode::{BorrowDecode, Decode, Encode}; -use dashcore::blsful::Bls12381G2Impl; #[cfg(feature = "core-types-serialization")] use dashcore::hashes::Hash; use dashcore::{ProTxHash, QuorumHash}; @@ -33,14 +32,9 @@ pub struct ValidatorSetV0 { /// The list of masternodes pub members: BTreeMap, /// The threshold quorum public key - // `BlsPublicKey` is a dashcore type, so its serde wrapper lives in - // `serialization::dashcore::bls_pubkey` (now self-sufficient — no upstream - // dependency; accepts hex string or byte sequence through any deserializer). - #[cfg_attr( - feature = "serde-conversion", - serde(with = "crate::serialization::dashcore::bls_pubkey") - )] - pub threshold_public_key: BlsPublicKey, + // Tagged enum buffers can carry either hex strings or byte sequences. + #[cfg_attr(feature = "serde-conversion", serde(with = "crate::bls::serde"))] + pub threshold_public_key: BlsPublicKey, } impl Display for ValidatorSetV0 { @@ -67,7 +61,7 @@ impl Display for ValidatorSetV0 { pro_tx_hash, validator.node_ip )) .join(", "), - hex::encode(self.threshold_public_key.0.to_compressed()) // Assuming BlsPublicKey is a byte array + hex::encode(self.threshold_public_key.to_bytes()) // Assuming BlsPublicKey is a byte array ) } } @@ -91,7 +85,7 @@ impl Encode for ValidatorSetV0 { // Custom encoding for BlsPublicKey if needed // Assuming BlsPublicKey can be serialized to a byte slice - let public_key_bytes = self.threshold_public_key.0.to_compressed(); + let public_key_bytes = self.threshold_public_key.to_bytes(); public_key_bytes.encode(encoder)?; Ok(()) @@ -237,7 +231,7 @@ pub trait ValidatorSetV0Getters { /// Returns the members of the validator set. fn members_owned(self) -> BTreeMap; /// Returns the threshold public key of the validator set. - fn threshold_public_key(&self) -> &BlsPublicKey; + fn threshold_public_key(&self) -> &BlsPublicKey; } /// Trait providing setter methods for `ValidatorSetV0` struct @@ -251,7 +245,7 @@ pub trait ValidatorSetV0Setters { /// Sets the members of the validator set. fn set_members(&mut self, members: BTreeMap); /// Sets the threshold public key of the validator set. - fn set_threshold_public_key(&mut self, threshold_public_key: BlsPublicKey); + fn set_threshold_public_key(&mut self, threshold_public_key: BlsPublicKey); } impl ValidatorSetV0Getters for ValidatorSetV0 { @@ -279,7 +273,7 @@ impl ValidatorSetV0Getters for ValidatorSetV0 { self.members } - fn threshold_public_key(&self) -> &BlsPublicKey { + fn threshold_public_key(&self) -> &BlsPublicKey { &self.threshold_public_key } } @@ -301,7 +295,7 @@ impl ValidatorSetV0Setters for ValidatorSetV0 { self.members = members; } - fn set_threshold_public_key(&mut self, threshold_public_key: BlsPublicKey) { + fn set_threshold_public_key(&mut self, threshold_public_key: BlsPublicKey) { self.threshold_public_key = threshold_public_key; } } @@ -309,8 +303,8 @@ impl ValidatorSetV0Setters for ValidatorSetV0 { #[cfg(test)] mod tests { use super::*; + use crate::bls::SecretKey; use bincode::config; - use dashcore::blsful::SecretKey; use dashcore::PubkeyHash; use rand::rngs::StdRng; use rand::SeedableRng; @@ -326,7 +320,7 @@ mod tests { // Create a sample ProTxHash and ValidatorV0 instance let pro_tx_hash = ProTxHash::from_slice(&[2; 32]).unwrap(); let mut rng = StdRng::seed_from_u64(0); - let public_key = Some(SecretKey::::random(&mut rng).public_key()); + let public_key = Some(SecretKey::random(&mut rng).public_key()); let node_ip = "192.168.1.1".to_string(); let node_id = PubkeyHash::from_slice(&[4; 20]).unwrap(); let validator = ValidatorV0 { @@ -345,7 +339,7 @@ mod tests { members.insert(pro_tx_hash, validator); // Create a sample threshold public key - let threshold_public_key = SecretKey::::random(&mut rng).public_key(); + let threshold_public_key = SecretKey::random(&mut rng).public_key(); // Create the ValidatorSetV0 instance let validator_set = ValidatorSetV0 { diff --git a/packages/rs-dpp/src/errors/protocol_error.rs b/packages/rs-dpp/src/errors/protocol_error.rs index 825bf832991..1cc5106db22 100644 --- a/packages/rs-dpp/src/errors/protocol_error.rs +++ b/packages/rs-dpp/src/errors/protocol_error.rs @@ -290,7 +290,7 @@ pub enum ProtocolError { /// BLS signature error #[cfg(feature = "bls-signatures")] #[error(transparent)] - BlsError(#[from] dashcore::blsful::BlsError), + BlsError(#[from] crate::bls::BlsError), #[error("Private key wrong size: expected 32, got {got}")] PrivateKeySizeError { got: u32 }, diff --git a/packages/rs-dpp/src/identity/identity_public_key/key_type.rs b/packages/rs-dpp/src/identity/identity_public_key/key_type.rs index 0914d245a04..260fc6b3a9d 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/key_type.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/key_type.rs @@ -7,13 +7,12 @@ use ciborium::value::Value as CborValue; use dashcore::secp256k1::rand::rngs::StdRng as EcdsaRng; #[cfg(feature = "random-public-keys")] use dashcore::secp256k1::rand::SeedableRng; -use dashcore::secp256k1::Secp256k1; use dashcore::Network; use itertools::Itertools; use lazy_static::lazy_static; #[cfg(feature = "bls-signatures")] -use crate::bls_signatures::{self as bls_signatures, Bls12381G2Impl, BlsError}; +use crate::bls::{self, BlsError}; use crate::fee::Credits; use crate::version::PlatformVersion; use crate::ProtocolError; @@ -161,15 +160,16 @@ impl KeyType { fn random_public_key_data_v0(&self, rng: &mut StdRng) -> Vec { match self { KeyType::ECDSA_SECP256K1 => { - let secp = Secp256k1::new(); - let mut rng = EcdsaRng::from_rng(rng).unwrap(); + let mut seed = [0u8; 32]; + rng.fill(&mut seed); + let mut rng = EcdsaRng::from_seed(seed); let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); let private_key = dashcore::PrivateKey::new(secret_key, Network::Mainnet); - private_key.public_key(&secp).to_bytes() + private_key.public_key().to_bytes() } KeyType::BLS12_381 => { - let private_key = bls_signatures::SecretKey::::random(rng); - private_key.public_key().0.to_compressed().to_vec() + let private_key = bls::SecretKey::random(rng); + private_key.public_key().to_bytes().to_vec() } KeyType::ECDSA_HASH160 | KeyType::BIP13_SCRIPT_HASH | KeyType::EDDSA_25519_HASH160 => { (0..self.default_size()).map(|_| rng.gen::()).collect() @@ -207,28 +207,25 @@ impl KeyType { ) -> Result, ProtocolError> { match self { KeyType::ECDSA_SECP256K1 => { - let secp = Secp256k1::new(); - let secret_key = dashcore::secp256k1::SecretKey::from_byte_array(private_key_bytes) - .map_err(|e| ProtocolError::Generic(e.to_string()))?; + let secret_key = + dashcore::secp256k1::SecretKey::from_secret_bytes(*private_key_bytes) + .map_err(|e| ProtocolError::Generic(e.to_string()))?; let private_key = dashcore::PrivateKey::new(secret_key, network); - Ok(private_key.public_key(&secp).to_bytes()) + Ok(private_key.public_key().to_bytes()) } KeyType::BLS12_381 => { #[cfg(feature = "bls-signatures")] { - let private_key: Option> = - bls_signatures::SecretKey::::from_be_bytes( - private_key_bytes, - ) - .into(); + let private_key: Option = + bls::SecretKey::from_be_bytes(private_key_bytes); if private_key.is_none() { return Err(ProtocolError::BlsError(BlsError::DeserializationError( "private key bytes not a valid secret key".to_string(), ))); } let private_key = private_key.expect("expected private key"); - let public_key_bytes = private_key.public_key().0.to_compressed().to_vec(); + let public_key_bytes = private_key.public_key().to_bytes().to_vec(); Ok(public_key_bytes) } #[cfg(not(feature = "bls-signatures"))] @@ -237,18 +234,17 @@ impl KeyType { )); } KeyType::ECDSA_HASH160 => { - let secp = Secp256k1::new(); - let secret_key = dashcore::secp256k1::SecretKey::from_byte_array(private_key_bytes) - .map_err(|e| ProtocolError::Generic(e.to_string()))?; + let secret_key = + dashcore::secp256k1::SecretKey::from_secret_bytes(*private_key_bytes) + .map_err(|e| ProtocolError::Generic(e.to_string()))?; let private_key = dashcore::PrivateKey::new(secret_key, network); - Ok(ripemd160_sha256(private_key.public_key(&secp).to_bytes().as_slice()).to_vec()) + Ok(ripemd160_sha256(private_key.public_key().to_bytes().as_slice()).to_vec()) } KeyType::EDDSA_25519_HASH160 => { #[cfg(feature = "ed25519-dalek")] { - let key_pair = - dashcore::ed25519_dalek::SigningKey::from_bytes(private_key_bytes); + let key_pair = crate::ed25519_dalek::SigningKey::from_bytes(private_key_bytes); Ok(ripemd160_sha256(key_pair.verifying_key().to_bytes().as_slice()).to_vec()) } #[cfg(not(feature = "ed25519-dalek"))] @@ -267,32 +263,34 @@ impl KeyType { pub fn random_public_and_private_key_data_v0(&self, rng: &mut StdRng) -> (Vec, [u8; 32]) { match self { KeyType::ECDSA_SECP256K1 => { - let secp = Secp256k1::new(); - let mut rng = EcdsaRng::from_rng(rng).unwrap(); + let mut seed = [0u8; 32]; + rng.fill(&mut seed); + let mut rng = EcdsaRng::from_seed(seed); let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); let private_key = dashcore::PrivateKey::new(secret_key, Network::Mainnet); ( - private_key.public_key(&secp).to_bytes(), - private_key.inner.secret_bytes(), + private_key.public_key().to_bytes(), + private_key.inner.to_secret_bytes(), ) } KeyType::BLS12_381 => { - let private_key = dashcore::blsful::SecretKey::::random(rng); - let public_key_bytes = private_key.public_key().0.to_compressed().to_vec(); - (public_key_bytes, private_key.0.to_be_bytes()) + let private_key = crate::bls::SecretKey::random(rng); + let public_key_bytes = private_key.public_key().to_bytes().to_vec(); + (public_key_bytes, private_key.to_be_bytes()) } KeyType::ECDSA_HASH160 => { - let secp = Secp256k1::new(); - let mut rng = EcdsaRng::from_rng(rng).unwrap(); + let mut seed = [0u8; 32]; + rng.fill(&mut seed); + let mut rng = EcdsaRng::from_seed(seed); let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); let private_key = dashcore::PrivateKey::new(secret_key, Network::Mainnet); ( - ripemd160_sha256(private_key.public_key(&secp).to_bytes().as_slice()).to_vec(), - private_key.inner.secret_bytes(), + ripemd160_sha256(private_key.public_key().to_bytes().as_slice()).to_vec(), + private_key.inner.to_secret_bytes(), ) } KeyType::EDDSA_25519_HASH160 => { - let key_pair = dashcore::ed25519_dalek::SigningKey::generate(rng); + let key_pair = crate::ed25519_dalek::SigningKey::generate(rng); ( ripemd160_sha256(key_pair.verifying_key().to_bytes().as_slice()).to_vec(), key_pair.to_bytes(), @@ -300,13 +298,14 @@ impl KeyType { } KeyType::BIP13_SCRIPT_HASH => { //todo (using ECDSA_HASH160 for now) - let secp = Secp256k1::new(); - let mut rng = EcdsaRng::from_rng(rng).unwrap(); + let mut seed = [0u8; 32]; + rng.fill(&mut seed); + let mut rng = EcdsaRng::from_seed(seed); let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); let private_key = dashcore::PrivateKey::new(secret_key, Network::Mainnet); ( - ripemd160_sha256(private_key.public_key(&secp).to_bytes().as_slice()).to_vec(), - private_key.inner.secret_bytes(), + ripemd160_sha256(private_key.public_key().to_bytes().as_slice()).to_vec(), + private_key.inner.to_secret_bytes(), ) } } diff --git a/packages/rs-dpp/src/identity/identity_public_key/v0/methods/mod.rs b/packages/rs-dpp/src/identity/identity_public_key/v0/methods/mod.rs index f1faa9d14e3..2f7034433b9 100644 --- a/packages/rs-dpp/src/identity/identity_public_key/v0/methods/mod.rs +++ b/packages/rs-dpp/src/identity/identity_public_key/v0/methods/mod.rs @@ -1,18 +1,17 @@ +#[cfg(feature = "bls-signatures")] +use crate::bls; +#[cfg(feature = "ed25519-dalek")] +use crate::ed25519_dalek; use crate::identity::identity_public_key::methods::hash::IdentityPublicKeyHashMethodsV0; use crate::identity::identity_public_key::v0::IdentityPublicKeyV0; use crate::identity::KeyType; use crate::util::hash::ripemd160_sha256; use crate::ProtocolError; use anyhow::anyhow; -#[cfg(feature = "ed25519-dalek")] -use dashcore::ed25519_dalek; use dashcore::hashes::Hash; -use dashcore::key::Secp256k1; use dashcore::secp256k1::SecretKey; use dashcore::{Network, PublicKey as ECDSAPublicKey}; use platform_value::{BinaryData, Bytes20}; -#[cfg(feature = "bls-signatures")] -use {crate::bls_signatures, dashcore::blsful::Bls12381G2Impl}; impl IdentityPublicKeyHashMethodsV0 for IdentityPublicKeyV0 { /// Get the original public key hash fn public_key_hash(&self) -> Result<[u8; 20], ProtocolError> { @@ -83,27 +82,25 @@ pub(in crate::identity::identity_public_key) fn validate_private_key_bytes_for_k ) -> Result { match key_type { KeyType::ECDSA_SECP256K1 => { - let secp = Secp256k1::new(); - let secret_key = match SecretKey::from_byte_array(private_key_bytes) { + let secret_key = match SecretKey::from_secret_bytes(*private_key_bytes) { Ok(secret_key) => secret_key, Err(_) => return Ok(false), }; let private_key = dashcore::PrivateKey::new(secret_key, network); - Ok(private_key.public_key(&secp).to_bytes() == data.as_slice()) + Ok(private_key.public_key().to_bytes() == data.as_slice()) } KeyType::BLS12_381 => { #[cfg(feature = "bls-signatures")] { - let private_key: Option> = - bls_signatures::SecretKey::::from_be_bytes(private_key_bytes) - .into(); + let private_key: Option = + bls::SecretKey::from_be_bytes(private_key_bytes); if private_key.is_none() { return Ok(false); } let private_key = private_key.expect("expected private key"); - Ok(private_key.public_key().0.to_compressed() == data.as_slice()) + Ok(private_key.public_key().to_bytes() == data.as_slice()) } #[cfg(not(feature = "bls-signatures"))] return Err(ProtocolError::NotSupported( @@ -111,15 +108,14 @@ pub(in crate::identity::identity_public_key) fn validate_private_key_bytes_for_k )); } KeyType::ECDSA_HASH160 => { - let secp = Secp256k1::new(); - let secret_key = match SecretKey::from_byte_array(private_key_bytes) { + let secret_key = match SecretKey::from_secret_bytes(*private_key_bytes) { Ok(secret_key) => secret_key, Err(_) => return Ok(false), }; let private_key = dashcore::PrivateKey::new(secret_key, network); Ok( - ripemd160_sha256(private_key.public_key(&secp).to_bytes().as_slice()).as_slice() + ripemd160_sha256(private_key.public_key().to_bytes().as_slice()).as_slice() == data.as_slice(), ) } @@ -146,8 +142,8 @@ pub(in crate::identity::identity_public_key) fn validate_private_key_bytes_for_k #[cfg(test)] mod tests { use super::*; + use crate::bls::Signature; use crate::identity::{Purpose, SecurityLevel}; - use dashcore::blsful::{Bls12381G2Impl, Pairing, Signature, SignatureSchemes}; use dashcore::Network; use dpp::version::PlatformVersion; use rand::rngs::StdRng; @@ -160,10 +156,9 @@ mod tests { .random_public_and_private_key_data(&mut rng, PlatformVersion::latest()) .expect("expected to get keys"); let decoded_secret_key = - dashcore::blsful::SecretKey::::from_be_bytes(&secret_key) - .expect("expected to get secret key"); + crate::bls::SecretKey::from_be_bytes(&secret_key).expect("expected to get secret key"); let public_key = decoded_secret_key.public_key(); - let decoded_public_key_data = public_key.0.to_compressed(); + let decoded_public_key_data = public_key.to_bytes(); assert_eq!( public_key_data.as_slice(), decoded_public_key_data.as_slice() @@ -177,18 +172,13 @@ mod tests { .random_public_and_private_key_data(&mut rng, PlatformVersion::latest()) .expect("expected to get keys"); let decoded_secret_key = - dashcore::blsful::SecretKey::::from_be_bytes(&secret_key) - .expect("expected to get secret key"); - let signature = decoded_secret_key - .sign(SignatureSchemes::Basic, b"hello") - .expect("expected to sign"); - let compressed = signature.as_raw_value().to_compressed(); - let g2 = ::Signature::from_compressed(&compressed) - .expect("G2 projective"); - let decoded_signature = Signature::::Basic(g2); + crate::bls::SecretKey::from_be_bytes(&secret_key).expect("expected to get secret key"); + let signature = decoded_secret_key.sign(b"hello").expect("expected to sign"); + let compressed = signature.to_bytes(); + let decoded_signature = Signature::from_compressed(&compressed).expect("valid signature"); assert_eq!( compressed.as_slice(), - decoded_signature.as_raw_value().to_compressed().as_slice() + decoded_signature.to_bytes().as_slice() ) } diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs index fe354a865a2..8b2c1560226 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs @@ -58,24 +58,22 @@ pub(super) fn validate_asset_lock_transaction_structure_v0( #[cfg(test)] mod tests { use super::*; - use dashcore::secp256k1::rand::thread_rng; - use dashcore::secp256k1::Secp256k1; + use dashcore::secp256k1::rand::rng; use dashcore::transaction::special_transaction::asset_lock::AssetLockPayload; use dashcore::{Network, OutPoint, PrivateKey, ScriptBuf, TxIn, Txid}; use std::str::FromStr; fn make_asset_lock_transaction(num_inputs: usize) -> Transaction { - let secp = Secp256k1::new(); - let mut rng = thread_rng(); + let mut rng = rng(); let input_secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); let private_key = PrivateKey::new(input_secret_key, Network::Testnet); - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); let public_key_hash = public_key.pubkey_hash(); let secret_key = dashcore::secp256k1::SecretKey::new(&mut rng); let one_time_private_key = PrivateKey::new(secret_key, Network::Testnet); - let one_time_public_key = one_time_private_key.public_key(&secp); + let one_time_public_key = one_time_private_key.public_key(); let one_time_key_hash = one_time_public_key.pubkey_hash(); let base_txid = diff --git a/packages/rs-dpp/src/lib.rs b/packages/rs-dpp/src/lib.rs index 23e18eb3c67..2a7ab144989 100644 --- a/packages/rs-dpp/src/lib.rs +++ b/packages/rs-dpp/src/lib.rs @@ -48,7 +48,7 @@ pub mod validation; #[cfg(feature = "client")] pub mod dash_platform_protocol; -mod bls; +pub mod bls; #[cfg(feature = "fixtures-and-mocks")] pub mod tests; @@ -86,7 +86,9 @@ pub mod withdrawal; pub use async_trait; -pub use bls::*; +#[cfg(feature = "bls-signatures")] +pub use bls::native_bls; +pub use bls::BlsModule; pub mod prelude { @@ -141,12 +143,10 @@ pub mod prelude { pub use bincode; pub use bincode::enc::Encode; -#[cfg(feature = "bls-signatures")] -pub use dashcore::blsful as bls_signatures; -#[cfg(feature = "ed25519-dalek")] -pub use dashcore::ed25519_dalek; #[cfg(feature = "data-contracts")] pub use data_contracts; +#[cfg(feature = "ed25519-dalek")] +pub use ed25519_dalek; #[cfg(feature = "jsonschema")] pub use jsonschema; pub use platform_serialization; diff --git a/packages/rs-dpp/src/serialization/dashcore/bls_pubkey.rs b/packages/rs-dpp/src/serialization/dashcore/bls_pubkey.rs deleted file mode 100644 index ccc6d8f852a..00000000000 --- a/packages/rs-dpp/src/serialization/dashcore/bls_pubkey.rs +++ /dev/null @@ -1,273 +0,0 @@ -//! Local serde wrapper for `BlsPublicKey` that tolerates -//! owned-string sources (`serde_json::Value`, `platform_value::Value`, and -//! anything routed through serde's `ContentDeserializer` for tagged-enum -//! buffering). -//! -//! ## Why this exists -//! -//! Upstream `blstrs_plus` 0.8.18 (`src/serde_impl.rs:119`) implements the -//! human-readable deserialize path as: -//! -//! ```ignore -//! if d.is_human_readable() { -//! let hex_str = <&str>::deserialize(d)?; // borrowed-only -//! ... -//! } -//! ``` -//! -//! `<&str>::deserialize` only succeeds when the deserializer's visitor -//! receives `visit_borrowed_str` — which `serde_json::from_slice` / -//! `serde_json::from_str` provide, but `serde_json::from_value`, -//! `platform_value::from_value`, and `ContentDeserializer` do **not** (they -//! produce owned `String`). Round-tripping a `BlsPublicKey` through any -//! `Value` representation therefore fails with -//! `"invalid type: string ..., expected a borrowed string"`. -//! -//! This is technically a serde compatibility quirk rather than a single -//! crate's bug — but the leaf type is the only place to patch. See plan -//! §10b "Common pattern: serde's `ContentDeserializer` HR-quirk" for -//! the broader narrative. -//! -//! ## How the workaround works -//! -//! A single `deserialize_any` visitor accepts BOTH wire forms upstream emits: -//! the 96-char hex string (`visit_str`) and the 48-byte compressed-G1 form as -//! raw bytes or a `u8` sequence (`visit_bytes` / `visit_seq`). Either way it -//! hex/byte-decodes to the compressed-G1 representation, lifts it to -//! `G1Projective` via `to_curve`, and wraps into `PublicKey` -//! directly (the inner field is `pub`). -//! -//! Driving it via `deserialize_any` (rather than branching on -//! `is_human_readable()`) is what makes it robust to serde's internal-tag -//! `Content` buffer: that buffer's `is_human_readable()` does not reliably -//! match the original deserializer, so a `Value` (non-HR) pubkey could arrive -//! as a byte *sequence* while the old HR branch expected a *string* — which is -//! exactly why the `ValidatorSet` value round-trip test used to be `#[ignore]`d. -//! Accepting both shapes in one visitor sidesteps that, and also sidesteps the -//! upstream borrowed-only `<&str>::deserialize` HR path. Both serde_json and -//! platform_value are self-describing, so `deserialize_any` is safe; bincode -//! never reaches here (it goes through the separate derived `Decode`). -//! -//! Note: `BlsPublicKey` carries a public key on the G1 curve -//! (the `Bls12381G2Impl` name refers to where signatures live, not keys). -//! Compressed G1 = 48 bytes = 96 hex chars. -//! -//! ## When to remove this -//! -//! This wrapper is now self-sufficient (no behavioral dependency on an upstream -//! fix). Once upstream `blstrs_plus` accepts owned strings AND a byte-sequence -//! HR form on its own `Deserialize`, this wrapper and the `serde(with = ...)` -//! annotations on `Validator::public_key` / `ValidatorSetV0::threshold_public_key` -//! can simply be dropped. - -use crate::bls_signatures::inner_types::{G1Affine, GroupEncoding, PrimeCurveAffine}; -use crate::bls_signatures::{Bls12381G2Impl, PublicKey as BlsPublicKey}; -use serde::de::Visitor; -use serde::{Deserializer, Serialize, Serializer}; -use std::fmt; - -/// Compressed-G1 wire size for BLS12-381 (where the public key lives in -/// `Bls12381G2Impl`). -const COMPRESSED_G1_LEN: usize = 48; - -pub fn serialize( - pk: &BlsPublicKey, - serializer: S, -) -> Result { - // Upstream serialize already produces a hex string in HR and a byte tuple - // in non-HR; both are correct on the wire. Nothing to override here. - pk.serialize(serializer) -} - -pub fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, -) -> Result, D::Error> { - // One visitor that accepts BOTH wire forms upstream emits: the - // human-readable 96-char hex string, and the non-HR 48-byte compressed-G1 - // form (as `bytes` or a `u8` sequence). Driving it via `deserialize_any` - // makes it robust to serde's internal-tag `Content` buffer, whose - // `is_human_readable()` does not always match the original deserializer — - // the bug that previously forced the ValidatorSet value round-trip test to - // be `#[ignore]`d (the non-HR bytes arrived while the old code's HR branch - // expected a string → "invalid type: sequence, expected a string"). It also - // sidesteps the upstream `<&str>::deserialize` borrowed-only HR path (the - // original reason this wrapper exists). Both serde_json and platform_value - // are self-describing, so `deserialize_any` is safe; bincode never reaches - // here (it goes through the separate derived `Decode`). - deserializer.deserialize_any(BlsPublicKeyVisitor) -} - -fn from_compressed_g1_bytes( - bytes: &[u8], -) -> Result, E> { - if bytes.len() != COMPRESSED_G1_LEN { - return Err(E::custom(format!( - "expected {COMPRESSED_G1_LEN} compressed-G1 bytes for public key, got {}", - bytes.len() - ))); - } - let mut compressed = ::Repr::default(); - compressed.as_mut().copy_from_slice(bytes); - let affine = Option::::from(G1Affine::from_bytes(&compressed)) - .ok_or_else(|| E::custom("not a valid compressed G1 point"))?; - Ok(BlsPublicKey::(affine.to_curve())) -} - -struct BlsPublicKeyVisitor; - -impl<'de> Visitor<'de> for BlsPublicKeyVisitor { - type Value = BlsPublicKey; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "a {}-char hex string or {} compressed-G1 bytes", - COMPRESSED_G1_LEN * 2, - COMPRESSED_G1_LEN - ) - } - - fn visit_str(self, s: &str) -> Result { - if s.len() != COMPRESSED_G1_LEN * 2 { - return Err(E::custom(format!( - "expected {} hex chars for compressed G1 public key, got {}", - COMPRESSED_G1_LEN * 2, - s.len() - ))); - } - let mut bytes = [0u8; COMPRESSED_G1_LEN]; - for (i, slot) in bytes.iter_mut().enumerate() { - let hi = hex_nibble(s.as_bytes()[i * 2]).map_err(E::custom)?; - let lo = hex_nibble(s.as_bytes()[i * 2 + 1]).map_err(E::custom)?; - *slot = (hi << 4) | lo; - } - from_compressed_g1_bytes(&bytes) - } - - fn visit_bytes(self, v: &[u8]) -> Result { - from_compressed_g1_bytes(v) - } - - fn visit_seq>(self, mut seq: A) -> Result { - let mut bytes = Vec::with_capacity(COMPRESSED_G1_LEN); - while let Some(b) = seq.next_element::()? { - // A valid compressed-G1 public key is exactly COMPRESSED_G1_LEN - // bytes; reject as soon as a hostile payload exceeds that rather - // than allocating/parsing an arbitrarily long sequence first. - if bytes.len() == COMPRESSED_G1_LEN { - return Err(serde::de::Error::invalid_length(bytes.len() + 1, &self)); - } - bytes.push(b); - } - from_compressed_g1_bytes(&bytes) - } -} - -fn hex_nibble(c: u8) -> Result { - match c { - b'0'..=b'9' => Ok(c - b'0'), - b'a'..=b'f' => Ok(c - b'a' + 10), - b'A'..=b'F' => Ok(c - b'A' + 10), - _ => Err("invalid hex character in compressed G1 public key"), - } -} - -/// `Option>` variant for fields like -/// `Validator::public_key`. -pub mod option { - use super::*; - - pub fn serialize( - opt: &Option>, - serializer: S, - ) -> Result { - // Option's built-in Serialize delegates to T's Serialize, which - // is the upstream BlsPublicKey impl — already correct. - opt.serialize(serializer) - } - - pub fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result>, D::Error> { - struct OptionVisitor; - - impl<'de> Visitor<'de> for OptionVisitor { - type Value = Option>; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Option>") - } - - fn visit_none(self) -> Result { - Ok(None) - } - - fn visit_unit(self) -> Result { - Ok(None) - } - - fn visit_some>( - self, - inner: D2, - ) -> Result { - super::deserialize(inner).map(Some) - } - } - - deserializer.deserialize_option(OptionVisitor) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde::{Deserialize, Serialize}; - use serde_json::json; - - // A known-valid compressed-G1 BLS public key (deterministic — the - // ValidatorSet fixture's seeded StdRng(42) threshold key). - const PK_HEX: &str = - "969c5d5873f49aa994c5f6a850924ca1840c4ad1791aaaecd90093d4a5c0c3799f2d98540f5366cfa0a33f143fd69263"; - - // Newtypes that drive the `with` module(s) through serde. - #[derive(Serialize, Deserialize)] - struct Wrap(#[serde(with = "super")] BlsPublicKey); - - #[derive(Serialize, Deserialize)] - struct OptWrap(#[serde(with = "super::option")] Option>); - - #[test] - fn json_hr_round_trip_is_the_hex_string() { - // Human-readable (serde_json::Value): hex in, identical hex out (visit_str). - let pk: Wrap = serde_json::from_value(json!(PK_HEX)).expect("from hex"); - assert_eq!(serde_json::to_value(&pk).expect("to json"), json!(PK_HEX)); - } - - #[test] - fn json_borrowed_str_path_works() { - // `serde_json::from_str` yields borrowed strings — the path upstream's - // `<&str>::deserialize` handled but `from_value`/Content did not. Our - // `visit_str` takes `&str`, so both work. - let pk: Wrap = serde_json::from_str(&format!("\"{PK_HEX}\"")).expect("from_str"); - assert_eq!(serde_json::to_value(&pk).expect("to json"), json!(PK_HEX)); - } - - #[test] - fn value_non_hr_byte_seq_round_trip() { - // Non-HR `platform_value` serializes the key as a 48-byte sequence; the - // `visit_seq` path (the bug the un-ignored ValidatorSet test exposed) - // must reconstruct it. Re-serialize to JSON to confirm the same key. - let pk: Wrap = serde_json::from_value(json!(PK_HEX)).expect("from hex"); - let value = platform_value::to_value(&pk).expect("to value"); - let pk2: Wrap = platform_value::from_value(value).expect("from value seq"); - assert_eq!(serde_json::to_value(&pk2).expect("to json"), json!(PK_HEX)); - } - - #[test] - fn option_some_and_none_round_trip() { - let some: OptWrap = serde_json::from_value(json!(PK_HEX)).expect("some"); - assert_eq!(serde_json::to_value(&some).expect("to json"), json!(PK_HEX)); - let none: OptWrap = serde_json::from_value(json!(null)).expect("none"); - assert_eq!(serde_json::to_value(&none).expect("to json"), json!(null)); - } -} diff --git a/packages/rs-dpp/src/serialization/dashcore/mod.rs b/packages/rs-dpp/src/serialization/dashcore/mod.rs index 65fb7e5cd97..c21d6baa443 100644 --- a/packages/rs-dpp/src/serialization/dashcore/mod.rs +++ b/packages/rs-dpp/src/serialization/dashcore/mod.rs @@ -1,18 +1,6 @@ -//! Serde `with` wrappers for types owned by external (dashcore) crates. -//! -//! These live here — rather than next to their dpp consumers — because the -//! type being (de)serialized is **not** a dpp type; it belongs to dashcore / -//! its transitive deps (e.g. `blstrs_plus`). Grouping them under -//! `serialization::dashcore` keeps "serde for out-of-crate types" in one place, -//! distinct from dpp-type-local serde (which lives next to its type). +//! Compatibility re-exports for Serde field adapters. -/// Compressed-G1 BLS public key (`BlsPublicKey` from dashcore's -/// `blstrs_plus`). Used via `#[serde(with = "crate::serialization::dashcore::bls_pubkey")]`. -/// -/// The wrapped type comes from `dashcore::blsful`, which is only linked when the -/// `bls-signatures` feature is on (`crate::bls_signatures`). Its only consumers — -/// the `core_types` validator/validator-set structs — are themselves gated behind -/// `core-types`, which requires `bls-signatures`, so gating here keeps the module -/// from breaking builds that omit BLS (e.g. `wasm-drive-verify`). +/// Accept hex strings or byte sequences for Platform BLS public keys. #[cfg(feature = "bls-signatures")] -pub mod bls_pubkey; +#[deprecated(note = "use dpp::bls::serde instead")] +pub use crate::bls::serde as bls_pubkey; diff --git a/packages/rs-dpp/src/serialization/mod.rs b/packages/rs-dpp/src/serialization/mod.rs index b50bf581cb3..a85bf4d5445 100644 --- a/packages/rs-dpp/src/serialization/mod.rs +++ b/packages/rs-dpp/src/serialization/mod.rs @@ -1,3 +1,5 @@ +#[cfg(any(feature = "serde-conversion", feature = "bls-signatures"))] +pub mod dashcore; #[cfg(feature = "json-conversion")] pub mod json; // Not gated behind `serde-conversion`: this fixed-size byte-array helper is @@ -5,8 +7,6 @@ pub mod json; // `block::extended_block_info::ExtendedBlockInfo`), so it must resolve in every // feature configuration. It only depends on `serde` and `base64`, both // non-optional dependencies. -#[cfg(feature = "serde-conversion")] -pub mod dashcore; pub mod serde_bytes; #[cfg(feature = "serde-conversion")] pub mod serde_bytes_var; diff --git a/packages/rs-dpp/src/signing.rs b/packages/rs-dpp/src/signing.rs index d637c0480b1..ba2256dd17b 100644 --- a/packages/rs-dpp/src/signing.rs +++ b/packages/rs-dpp/src/signing.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "bls-signatures")] +use crate::bls::{self, Signature}; use crate::identity::KeyType; use crate::serialization::PlatformMessageSignable; #[cfg(feature = "message-signature-verification")] @@ -10,11 +12,6 @@ use crate::{ #[cfg(feature = "message-signing")] use crate::{BlsModule, ProtocolError}; use dashcore::signer; -#[cfg(feature = "bls-signatures")] -use { - crate::bls_signatures::{Bls12381G2Impl, Pairing}, - dashcore::{blsful as bls_signatures, blsful::Signature}, -}; impl PlatformMessageSignable for &[u8] { #[cfg(feature = "message-signature-verification")] @@ -44,17 +41,15 @@ impl PlatformMessageSignable for &[u8] { } } KeyType::BLS12_381 => { - let public_key = - match bls_signatures::PublicKey::::try_from(public_key_data) { - Ok(public_key) => public_key, - Err(e) => { - // dbg!(format!("bls public_key could not be recovered")); - return SimpleConsensusValidationResult::new_with_error( - SignatureError::BasicBLSError(BasicBLSError::new(e.to_string())) - .into(), - ); - } - }; + let public_key = match bls::PublicKey::try_from(public_key_data) { + Ok(public_key) => public_key, + Err(e) => { + // dbg!(format!("bls public_key could not be recovered")); + return SimpleConsensusValidationResult::new_with_error( + SignatureError::BasicBLSError(BasicBLSError::new(e.to_string())).into(), + ); + } + }; let signature_bytes: [u8; 96] = match signature.try_into() { Ok(bytes) => bytes, Err(_) => { @@ -67,11 +62,7 @@ impl PlatformMessageSignable for &[u8] { ) } }; - let g2 = match ::Signature::from_compressed( - &signature_bytes, - ) - .into_option() - { + let signature = match Signature::from_compressed(&signature_bytes) { Some(g2) => g2, None => { return SimpleConsensusValidationResult::new_with_error( @@ -79,7 +70,6 @@ impl PlatformMessageSignable for &[u8] { ); } }; - let signature = Signature::::Basic(g2); if signature.verify(&public_key, signable_data).is_err() { SimpleConsensusValidationResult::new_with_error( diff --git a/packages/rs-dpp/src/state_transition/mod.rs b/packages/rs-dpp/src/state_transition/mod.rs index 0ba37f6aff4..bcc1338f0ac 100644 --- a/packages/rs-dpp/src/state_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/mod.rs @@ -2212,7 +2212,7 @@ impl StateTransition { signer: &S, ) -> Result<(), ProtocolError> { use dashcore::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; - use dashcore::secp256k1::{Message, Secp256k1}; + use dashcore::secp256k1::Message; use dashcore::signer::{double_sha, CompactSignature}; let data = self.signable_bytes()?; @@ -2237,7 +2237,6 @@ impl StateTransition { // `r||s` payload is bit-identical to what `dashcore::signer::sign` // produces. let compact_64 = signature.serialize_compact(); - let secp = Secp256k1::new(); let msg = Message::from_digest(digest); let mut found: Option = None; @@ -2250,7 +2249,7 @@ impl StateTransition { Ok(s) => s, Err(_) => continue, }; - if let Ok(recovered) = secp.recover_ecdsa(&msg, &candidate) { + if let Ok(recovered) = candidate.recover_ecdsa(msg) { if recovered == public_key { found = Some(candidate); break; @@ -4655,9 +4654,7 @@ mod tests { #[tokio::test] async fn sign_with_core_signer_matches_sign_by_private_key_byte_for_byte() { use async_trait::async_trait; - use dashcore::secp256k1::{ - ecdsa, rand::rngs::OsRng, Message, PublicKey, Secp256k1, SecretKey, - }; + use dashcore::secp256k1::{self, ecdsa, rand, Message, PublicKey, SecretKey}; use key_wallet::bip32::{DerivationPath, ExtendedPubKey}; use key_wallet::signer::{ExtendedPubKeySigner, Signer as KwSigner, SignerMethod}; @@ -4684,9 +4681,8 @@ mod tests { _path: &DerivationPath, sighash: [u8; 32], ) -> Result<(ecdsa::Signature, PublicKey), Self::Error> { - let secp = Secp256k1::new(); let msg = Message::from_digest(sighash); - let sig = secp.sign_ecdsa(&msg, &self.secret); + let sig = self.secret.sign_ecdsa(msg); Ok((sig, self.public)) } @@ -4709,9 +4705,8 @@ mod tests { // load-bearing: the legacy path signs raw bytes, the signer path // derives + signs inside the trust boundary. If the digest pre-image // or compact-encoding differs, the bytes will diverge. - let secp = Secp256k1::new(); - let (secret_key, public_key) = secp.generate_keypair(&mut OsRng); - let private_key_bytes = secret_key.secret_bytes(); + let (secret_key, public_key) = secp256k1::generate_keypair(&mut rand::rng()); + let private_key_bytes = secret_key.to_secret_bytes(); let signer = FixedKeySigner { secret: secret_key, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funding_from_asset_lock_transition/signing_tests.rs b/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funding_from_asset_lock_transition/signing_tests.rs index 8719b18ea90..fa7c6f13398 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funding_from_asset_lock_transition/signing_tests.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funding_from_asset_lock_transition/signing_tests.rs @@ -14,7 +14,7 @@ use std::collections::{BTreeMap, HashMap}; use dashcore::hashes::Hash; -use dashcore::secp256k1::{PublicKey as RawPublicKey, Secp256k1, SecretKey as RawSecretKey}; +use dashcore::secp256k1::{PublicKey as RawPublicKey, SecretKey as RawSecretKey}; use dashcore::{OutPoint, PublicKey}; use platform_value::BinaryData; use platform_version::version::PlatformVersion; @@ -38,9 +38,8 @@ struct TestAddressSigner { impl TestAddressSigner { fn add_p2pkh(&mut self, seed: [u8; 32]) -> PlatformAddress { - let secp = Secp256k1::new(); - let secret = RawSecretKey::from_byte_array(&seed).expect("valid secret key"); - let public = PublicKey::new(RawPublicKey::from_secret_key(&secp, &secret)); + let secret = RawSecretKey::from_secret_bytes(seed).expect("valid secret key"); + let public = PublicKey::new(RawPublicKey::from_secret_key(&secret)); let hash = *public.pubkey_hash().as_byte_array(); self.keys.insert(hash, (secret, public)); PlatformAddress::P2pkh(hash) @@ -59,7 +58,7 @@ impl Signer for TestAddressSigner { .keys .get(hash) .ok_or_else(|| ProtocolError::Generic(format!("unknown key {}", hex::encode(hash))))?; - let sig = dashcore::signer::sign(data, secret.as_ref()) + let sig = dashcore::signer::sign(data, secret.as_secret_bytes()) .map_err(|e| ProtocolError::Generic(e.to_string()))?; Ok(BinaryData::new(sig.to_vec())) } @@ -229,9 +228,8 @@ async fn try_from_asset_lock_with_signers_produces_matching_signature() { _path: &DerivationPath, sighash: [u8; 32], ) -> Result<(ecdsa::Signature, RawPublicKey), Self::Error> { - let secp = Secp256k1::new(); let msg = Message::from_digest(sighash); - Ok((secp.sign_ecdsa(&msg, &self.secret), self.public)) + Ok((self.secret.sign_ecdsa(msg), self.public)) } async fn public_key(&self, _path: &DerivationPath) -> Result { @@ -249,9 +247,8 @@ async fn try_from_asset_lock_with_signers_produces_matching_signature() { } } - let secp = Secp256k1::new(); - let asset_lock_secret = RawSecretKey::from_byte_array(&[7u8; 32]).expect("valid secret"); - let asset_lock_public = RawPublicKey::from_secret_key(&secp, &asset_lock_secret); + let asset_lock_secret = RawSecretKey::from_secret_bytes([7u8; 32]).expect("valid secret"); + let asset_lock_public = RawPublicKey::from_secret_key(&asset_lock_secret); let mut input_signer = TestAddressSigner::default(); let input_addr = input_signer.add_p2pkh([1u8; 32]); diff --git a/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funds_transfer_transition/signing_tests.rs b/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funds_transfer_transition/signing_tests.rs index 866e39f123c..6ac8d35825d 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funds_transfer_transition/signing_tests.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_funds_transfer_transition/signing_tests.rs @@ -13,7 +13,7 @@ use std::collections::{BTreeMap, HashMap}; use dashcore::blockdata::opcodes::all::*; use dashcore::blockdata::script::ScriptBuf; use dashcore::hashes::Hash; -use dashcore::secp256k1::{PublicKey as RawPublicKey, Secp256k1, SecretKey as RawSecretKey}; +use dashcore::secp256k1::{PublicKey as RawPublicKey, SecretKey as RawSecretKey}; use dashcore::PublicKey; use platform_value::BinaryData; @@ -64,16 +64,15 @@ impl TestAddressSigner { /// Creates a keypair from a 32-byte seed fn create_keypair(seed: [u8; 32]) -> (RawSecretKey, PublicKey) { - let secp = Secp256k1::new(); - let secret_key = RawSecretKey::from_byte_array(&seed).expect("valid secret key"); - let raw_public_key = RawPublicKey::from_secret_key(&secp, &secret_key); + let secret_key = RawSecretKey::from_secret_bytes(seed).expect("valid secret key"); + let raw_public_key = RawPublicKey::from_secret_key(&secret_key); let public_key = PublicKey::new(raw_public_key); (secret_key, public_key) } /// Signs data with a secret key fn sign_data(data: &[u8], secret_key: &RawSecretKey) -> Vec { - dashcore::signer::sign(data, secret_key.as_ref()) + dashcore::signer::sign(data, secret_key.as_secret_bytes()) .expect("signing should succeed") .to_vec() } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/signing_tests.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/signing_tests.rs index 678d9cd8b8e..542dd3fb724 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/signing_tests.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/signing_tests.rs @@ -33,7 +33,7 @@ use dashcore::OutPoint; use platform_version::version::PlatformVersion; use async_trait::async_trait; -use dashcore::secp256k1::{ecdsa, Message, PublicKey, Secp256k1, SecretKey}; +use dashcore::secp256k1::{ecdsa, Message, PublicKey, SecretKey}; use key_wallet::bip32::{DerivationPath, ExtendedPubKey}; use key_wallet::signer::{ExtendedPubKeySigner, Signer as KwSigner, SignerMethod}; @@ -50,9 +50,8 @@ struct FixedKeySigner { impl FixedKeySigner { fn new(seed: [u8; 32]) -> Self { - let secp = Secp256k1::new(); - let secret = SecretKey::from_byte_array(&seed).expect("valid secret"); - let public = PublicKey::from_secret_key(&secp, &secret); + let secret = SecretKey::from_secret_bytes(seed).expect("valid secret"); + let public = PublicKey::from_secret_key(&secret); Self { secret, public } } } @@ -70,9 +69,8 @@ impl KwSigner for FixedKeySigner { _path: &DerivationPath, sighash: [u8; 32], ) -> Result<(ecdsa::Signature, PublicKey), Self::Error> { - let secp = Secp256k1::new(); let msg = Message::from_digest(sighash); - Ok((secp.sign_ecdsa(&msg, &self.secret), self.public)) + Ok((self.secret.sign_ecdsa(msg), self.public)) } async fn public_key(&self, _path: &DerivationPath) -> Result { diff --git a/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs b/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs index f005be86412..ff38671e537 100644 --- a/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs +++ b/packages/rs-dpp/src/state_transition/traits/state_transition_identity_signed.rs @@ -106,11 +106,12 @@ pub trait StateTransitionIdentitySigned: StateTransitionLike { } pub fn get_compressed_public_ec_key(private_key: &[u8]) -> Result<[u8; 33], ProtocolError> { - let sk = RawSecretKey::from_slice(private_key) + let sk = <[u8; 32]>::try_from(private_key) + .map_err(|_| dashcore::secp256k1::Error::InvalidSecretKey) + .and_then(RawSecretKey::from_secret_bytes) .map_err(|e| anyhow!("Invalid ECDSA private key: {}", e))?; - let secp = dashcore::secp256k1::Secp256k1::new(); - let public_key_compressed = RawPublicKey::from_secret_key(&secp, &sk).serialize(); + let public_key_compressed = RawPublicKey::from_secret_key(&sk).serialize(); Ok(public_key_compressed) } diff --git a/packages/rs-dpp/src/tests/fixtures/identity_credit_withdrawal_transition_fixture.rs b/packages/rs-dpp/src/tests/fixtures/identity_credit_withdrawal_transition_fixture.rs index ca814d858fc..dd0193d38a2 100644 --- a/packages/rs-dpp/src/tests/fixtures/identity_credit_withdrawal_transition_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/identity_credit_withdrawal_transition_fixture.rs @@ -6,6 +6,7 @@ use platform_value::string_encoding::{encode, Encoding}; use platform_value::BinaryData; use platform_value::{platform_value, Value}; use serde_json::{json, Value as JsonValue}; +use std::str::FromStr; use crate::withdrawal::Pooling; use crate::{state_transition::StateTransitionType, version}; @@ -18,7 +19,7 @@ pub fn identity_credit_withdrawal_transition_fixture_raw_object() -> Value { "amount": 1042u64, "coreFeePerByte": 3u32, "pooling": Pooling::Never as u8, - "outputScript": CoreScript::new(ScriptBuf::new_p2pkh(&PubkeyHash::from_hex("0000000000000000000000000000000000000000").unwrap())), + "outputScript": CoreScript::new(ScriptBuf::new_p2pkh(&PubkeyHash::from_str("0000000000000000000000000000000000000000").unwrap())), "revision": 1 as Revision, "signaturePublicKeyId": 0u32, "signature": BinaryData::new(vec![0_u8; 65]), @@ -33,7 +34,7 @@ pub fn identity_credit_withdrawal_transition_fixture_json() -> JsonValue { "amount": 1042, "coreFeePerByte": 3, "pooling": Pooling::Never, - "outputScript": encode(&ScriptBuf::new_p2pkh(&PubkeyHash::from_hex("0000000000000000000000000000000000000000").unwrap()).to_bytes(), Encoding::Base64), + "outputScript": encode(&ScriptBuf::new_p2pkh(&PubkeyHash::from_str("0000000000000000000000000000000000000000").unwrap()).to_bytes(), Encoding::Base64), "signature": encode(&[0_u8; 65], Encoding::Base64), "signaturePublicKeyId": 0, "revision": 1, diff --git a/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs b/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs index f55590029df..23e620c6f81 100644 --- a/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs @@ -4,8 +4,7 @@ use dashcore::bls_sig_utils::BLSSignature; use dashcore::hash_types::CycleHash; use crate::balances::credits::Duffs; -use dashcore::secp256k1::rand::thread_rng; -use dashcore::secp256k1::Secp256k1; +use dashcore::secp256k1::rand::rng; use dashcore::transaction::special_transaction::asset_lock::AssetLockPayload; use dashcore::transaction::special_transaction::TransactionPayload; use dashcore::{ @@ -46,18 +45,17 @@ pub fn instant_asset_lock_proof_transaction_fixture( one_time_private_key: Option, amount: Option, ) -> Transaction { - let mut rng = thread_rng(); - let secp = Secp256k1::new(); + let mut rng = rng(); let private_key_hex = "cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY"; let private_key = PrivateKey::from_str(private_key_hex).unwrap(); - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); let public_key_hash = public_key.pubkey_hash(); //let from_address = Address::p2pkh(&public_key, Network::Testnet); let secret_key = SecretKey::new(&mut rng); let one_time_private_key = one_time_private_key.unwrap_or_else(|| PrivateKey::new(secret_key, Network::Testnet)); - let one_time_public_key = one_time_private_key.public_key(&secp); + let one_time_public_key = one_time_private_key.public_key(); // We are going to fund 1 Dash and // assume that input has 100005000 diff --git a/packages/rs-dpp/tests/bls_compatibility.rs b/packages/rs-dpp/tests/bls_compatibility.rs new file mode 100644 index 00000000000..c14ff906f7b --- /dev/null +++ b/packages/rs-dpp/tests/bls_compatibility.rs @@ -0,0 +1,388 @@ +use dash_pkc::bls::{BlsPublicKey, BlsScChia, BlsScIetf, BlsSecretKey, BlsSignature}; +use dpp::bls::{PublicKey, SecretKey, Signature}; +use dpp::core_types::validator::v0::ValidatorV0; +use dpp::core_types::validator_set::v0::ValidatorSetV0; +use dpp::native_bls::NativeBlsModule; +use dpp::{BlsModule, ProtocolError}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Corpus { + key_generation: Vec, + basic: Vec, + secure_aggregation: Vec, + scalars: Vec, + storage: Vec, +} + +#[derive(Deserialize)] +struct KeyGeneration { + ikm: String, + secret_key: String, + public_key: String, +} + +#[derive(Deserialize)] +struct Basic { + secret_key: String, + message: String, + public_key: String, + legacy_public_key: String, + signature: String, +} + +#[derive(Deserialize)] +struct Aggregate { + secret_keys: Vec, + message: String, + public_keys: Vec, + signature: String, +} + +#[derive(Deserialize)] +struct Scalar { + name: String, + input: String, + normalized: Option, + public_key: Option, + signature: Option, +} + +#[derive(Deserialize)] +struct Storage { + name: String, + validator: serde_json::Value, + validator_bytes: String, + validator_set: serde_json::Value, + validator_set_bytes: String, +} + +fn corpus() -> Corpus { + serde_json::from_str(include_str!("fixtures/bls_compatibility/vectors.json")).unwrap() +} + +fn bytes(hex: &str) -> [u8; N] { + hex::decode(hex).unwrap().try_into().unwrap() +} + +#[test] +fn should_preserve_key_generation_from_fixed_material() { + let vectors = corpus().key_generation; + assert_eq!(vectors.len(), 3); + for v in vectors { + let ikm = bytes::<32>(&v.ikm); + let current = SecretKey::from_ikm(&ikm).unwrap(); + let candidate = BlsSecretKey::::from_ikm(&ikm).unwrap(); + assert_eq!(current.to_be_bytes(), bytes::<32>(&v.secret_key)); + assert_eq!(*candidate.to_bytes(), current.to_be_bytes()); + assert_eq!(current.public_key().to_bytes(), bytes::<48>(&v.public_key)); + assert_eq!( + candidate.public_key().to_bytes(), + bytes::<48>(&v.public_key) + ); + } +} + +#[test] +fn should_match_frozen_basic_signatures_in_both_backends() { + let vectors = corpus().basic; + assert_eq!(vectors.len(), 16); + for v in vectors { + let secret = bytes(&v.secret_key); + let message = hex::decode(v.message).unwrap(); + let public: [u8; 48] = bytes(&v.public_key); + let signature: [u8; 96] = bytes(&v.signature); + assert_eq!( + NativeBlsModule.private_key_to_public_key(&secret).unwrap(), + public + ); + assert_eq!(NativeBlsModule.sign(&message, &secret).unwrap(), signature); + assert!(NativeBlsModule + .verify_signature(&signature, &message, &public) + .unwrap()); + let candidate = BlsSecretKey::::from_bytes(&secret).unwrap(); + assert_eq!(candidate.public_key().to_bytes(), public); + assert_eq!(candidate.sign(&message).to_bytes(), signature); + let decoded = BlsSignature::::from_bytes(&signature).unwrap(); + assert!(candidate.public_key().verify(&message, &decoded).is_ok()); + assert_eq!( + PublicKey::try_from(public.as_slice()) + .unwrap() + .to_legacy_bytes() + .unwrap(), + bytes::<48>(&v.legacy_public_key) + ); + assert_eq!( + SecretKey::from_be_bytes(&secret) + .unwrap() + .public_key() + .to_legacy_bytes() + .unwrap(), + bytes::<48>(&v.legacy_public_key) + ); + assert_eq!( + candidate + .public_key() + .to_scheme::() + .unwrap() + .to_bytes(), + bytes::<48>(&v.legacy_public_key) + ); + let mut wrong_message = message.clone(); + wrong_message.push(1); + assert!(!NativeBlsModule + .verify_signature(&signature, &wrong_message, &public) + .unwrap()); + assert!(candidate + .public_key() + .verify(&wrong_message, &decoded) + .is_err()); + let mut other_secret = [0; 32]; + other_secret[31] = 3; + let other = BlsSecretKey::::from_bytes(&other_secret) + .unwrap() + .public_key(); + assert!(!NativeBlsModule + .verify_signature(&signature, &message, &other.to_bytes()) + .unwrap()); + assert!(other.verify(&message, &decoded).is_err()); + } +} + +#[test] +fn should_match_frozen_secure_aggregates_in_both_backends() { + let vectors = corpus().secure_aggregation; + assert_eq!(vectors.len(), 5); + for v in vectors { + let message = hex::decode(v.message).unwrap(); + let public_keys: Vec<_> = v + .public_keys + .iter() + .map(|key| PublicKey::try_from(bytes::<48>(key).as_slice()).unwrap()) + .collect(); + let signatures: Vec<_> = v + .secret_keys + .iter() + .map(|key| { + SecretKey::from_be_bytes(&bytes(key)) + .unwrap() + .sign(&message) + .unwrap() + }) + .collect(); + let point = Signature::aggregate_secure(&signatures, &public_keys).unwrap(); + assert_eq!(point.to_bytes(), bytes::<96>(&v.signature)); + assert!(point.verify_secure(&public_keys, &message).is_ok()); + let candidate_keys: Vec<_> = v + .secret_keys + .iter() + .map(|key| BlsSecretKey::::from_bytes(&bytes(key)).unwrap()) + .collect(); + let pks: Vec<_> = candidate_keys.iter().map(|key| key.public_key()).collect(); + let sigs: Vec<_> = candidate_keys + .iter() + .map(|key| key.sign(&message)) + .collect(); + let refs: Vec<_> = pks.iter().collect(); + let candidate = + BlsSignature::secure_aggregate(&sigs.iter().collect::>(), &refs).unwrap(); + assert_eq!(candidate.to_bytes(), bytes::<96>(&v.signature)); + assert!(candidate.secure_verify_aggregates(&message, &refs).is_ok()); + let mut wrong_message = message; + wrong_message.push(1); + assert!(point.verify_secure(&public_keys, &wrong_message).is_err()); + assert!(candidate + .secure_verify_aggregates(&wrong_message, &refs) + .is_err()); + } +} + +#[test] +fn should_preserve_historical_scalar_reduction() { + let vectors = corpus().scalars; + assert_eq!(vectors.len(), 4); + for v in vectors { + let input = bytes(&v.input); + let key = SecretKey::from_be_bytes(&input); + assert_eq!( + key.as_ref().map(|key| hex::encode(key.to_be_bytes())), + v.normalized, + "{}", + v.name + ); + // The strict upstream constructor cannot replace the historical DPP parser directly. + assert!( + BlsSecretKey::::from_bytes(&input).is_err(), + "{}", + v.name + ); + if let Some(normalized) = v.normalized { + assert_eq!( + hex::encode(NativeBlsModule.private_key_to_public_key(&input).unwrap()), + v.public_key.unwrap() + ); + assert_eq!( + hex::encode(NativeBlsModule.sign(b"scalar boundary", &input).unwrap()), + v.signature.unwrap() + ); + let reduced = dash_pkc::bls::Fr::from_bendian_reduce(&input).unwrap(); + let candidate = BlsSecretKey::::try_from(reduced).unwrap(); + assert_eq!(*candidate.to_bytes(), bytes::<32>(&normalized)); + } else { + assert!(matches!( + NativeBlsModule.sign(b"scalar boundary", &input), + Err(ProtocolError::InvalidBLSPrivateKeyError(_)) + )); + assert!(matches!( + NativeBlsModule.private_key_to_public_key(&input), + Err(ProtocolError::InvalidBLSPrivateKeyError(_)) + )); + } + } +} + +#[test] +fn should_preserve_identity_key_parsing_without_accepting_identity_signatures() { + let mut public_key = [0; 48]; + public_key[0] = 0xc0; + let mut signature = [0; 96]; + signature[0] = 0xc0; + // This is historical parsing behavior, not permission to verify an identity signature. + assert!(NativeBlsModule.validate_public_key(&public_key).is_ok()); + assert!(BlsPublicKey::::from_bytes(&public_key).is_err()); + assert!(!NativeBlsModule + .verify_signature(&signature, b"identity", &public_key) + .unwrap()); + let valid = bytes::<48>(&corpus().basic[0].public_key); + assert!(!NativeBlsModule + .verify_signature(&signature, b"identity", &valid) + .unwrap()); + + let identity_key = PublicKey::try_from(public_key.as_slice()).unwrap(); + let identity_signature = Signature::from_compressed(&signature).unwrap(); + assert_eq!(identity_key, PublicKey::default()); + assert_eq!(identity_key.to_bytes(), public_key); + assert_eq!(identity_key.to_legacy_bytes().unwrap(), public_key); + assert_eq!(identity_signature.to_bytes(), signature); + + let v = &corpus().basic[0]; + let message = hex::decode(&v.message).unwrap(); + let valid_key = PublicKey::try_from(valid.as_slice()).unwrap(); + let valid_signature = Signature::from_compressed(&bytes(&v.signature)).unwrap(); + assert!(valid_signature.verify(&identity_key, &message).is_err()); + assert!(identity_signature.verify(&valid_key, &message).is_err()); + assert!(valid_signature + .verify_secure(&[identity_key], &message) + .is_err()); + assert!(identity_signature + .verify_secure(&[valid_key], &message) + .is_err()); + assert!(Signature::aggregate_secure(&[identity_signature], &[valid_key]).is_err()); + assert!(Signature::aggregate_secure(&[valid_signature], &[identity_key]).is_err()); +} + +#[test] +fn should_preserve_invalid_input_results_and_validation_order() { + let v = &corpus().basic[0]; + let public_key = bytes::<48>(&v.public_key); + let secret = bytes::<32>(&v.secret_key); + for len in [0, 31, 33] { + assert!( + matches!(NativeBlsModule.sign(b"", &vec![1;len]), Err(ProtocolError::PrivateKeySizeError { got }) if got == len as u32) + ); + assert!( + matches!(NativeBlsModule.private_key_to_public_key(&vec![1;len]), Err(ProtocolError::PrivateKeySizeError { got }) if got == len as u32) + ); + } + for len in [0, 95, 97] { + assert!( + matches!(NativeBlsModule.verify_signature(&vec![0;len], b"", &public_key), Err(ProtocolError::BlsSignatureSizeError { got }) if got == len as u32) + ); + assert!(matches!( + NativeBlsModule.verify_signature(&vec![0; len], b"", &[0; 48]), + Err(ProtocolError::BlsError(_)) + )); + } + for public in [vec![], vec![0; 47], vec![0; 48], vec![255; 48], vec![0; 49]] { + assert!(NativeBlsModule.validate_public_key(&public).is_err()); + assert!(matches!( + NativeBlsModule.verify_signature(&[0; 96], b"", &public), + Err(ProtocolError::BlsError(_)) + )); + } + for signature in [[0; 96], [255; 96]] { + assert!(!NativeBlsModule + .verify_signature(&signature, b"", &public_key) + .unwrap()); + assert!(BlsSignature::::from_bytes(&signature).is_err()); + } + assert!(NativeBlsModule.sign(b"", &secret).is_ok()); +} + +#[test] +fn should_reject_points_outside_the_prime_order_subgroup() { + // On-curve points x=4 in G1 and x=(2,0) in G2, in compressed IETF encoding. + let mut public_key = [0; 48]; + public_key[0] = 0x80; + public_key[47] = 4; + let mut signature = [0; 96]; + signature[0] = 0xa0; + signature[95] = 2; + assert!(NativeBlsModule.validate_public_key(&public_key).is_err()); + assert!(BlsPublicKey::::from_bytes(&public_key).is_err()); + assert!(Signature::from_compressed(&signature).is_none()); + let valid = bytes::<48>(&corpus().basic[0].public_key); + assert!(!NativeBlsModule + .verify_signature(&signature, b"", &valid) + .unwrap()); + assert!(BlsSignature::::from_bytes(&signature).is_err()); +} + +#[test] +fn should_read_and_reemit_frozen_validator_storage() { + let config = bincode::config::standard().with_big_endian(); + let vectors = corpus().storage; + assert_eq!(vectors.len(), 3); + for v in vectors { + let encoded = hex::decode(v.validator_bytes).unwrap(); + let (validator, consumed): (ValidatorV0, _) = + bincode::decode_from_slice(&encoded, config).unwrap(); + assert_eq!(consumed, encoded.len(), "{}", v.name); + assert_eq!(bincode::encode_to_vec(&validator, config).unwrap(), encoded); + assert_eq!(serde_json::to_value(&validator).unwrap(), v.validator); + if let Some(key) = &validator.public_key { + // QuorumForSavingV1 uses bincode(with_serde): exactly 48 bytes, with no length prefix. + let bytes = bincode::serde::encode_to_vec(key, config).unwrap(); + assert_eq!(bytes, key.to_bytes()); + let decoded = + bincode::serde::decode_from_slice::(&bytes, config).unwrap(); + assert_eq!(decoded, (*key, 48)); + } + assert_eq!( + serde_json::from_value::(v.validator.clone()).unwrap(), + validator + ); + assert_eq!( + bincode::decode_from_slice_untrusted::(&encoded, config) + .unwrap() + .0, + validator + ); + assert_eq!( + bincode::borrow_decode_from_slice_untrusted::(&encoded, config) + .unwrap() + .0, + validator + ); + let encoded = hex::decode(v.validator_set_bytes).unwrap(); + let (set, consumed): (ValidatorSetV0, _) = + bincode::decode_from_slice(&encoded, config).unwrap(); + assert_eq!(consumed, encoded.len()); + assert_eq!(bincode::encode_to_vec(&set, config).unwrap(), encoded); + assert_eq!(serde_json::to_value(&set).unwrap(), v.validator_set); + assert_eq!( + serde_json::from_value::(v.validator_set).unwrap(), + set + ); + } +} diff --git a/packages/rs-dpp/tests/fixtures/bls_compatibility/README.md b/packages/rs-dpp/tests/fixtures/bls_compatibility/README.md new file mode 100644 index 00000000000..752f676c763 --- /dev/null +++ b/packages/rs-dpp/tests/fixtures/bls_compatibility/README.md @@ -0,0 +1,37 @@ +# BLS compatibility vectors + +These public test keys and fixtures were generated before the backend migration, +from Platform `c0b4b97f8a1a8626c2f51bc8d7baeab9c65e10da` (#5307): + +- `dashpay/agora-blsful` at `0c34a7a488a0bd1c9a9a2196e793b303ad35c900`; +- `blstrs_plus` 0.8.18; +- `blst` at `71a00877c75a482e7d44c4e6370c1a7fb82444be` (0.3.12); +- candidate: `dash-pkc` at `e6402ced257c370a586ade9840ebbf545a4b7926`, + the backend used by rust-dashcore `314f10600e35311f3d875a6e8740476ab6c79c0c`. + +`vectors.json` contains 16 Basic signatures (four canonical scalars × empty, +text, zero digest and 256-byte messages), five secure aggregates (one/two/three +signers, reordered keys and a duplicate key), three key-generation cases, four +scalar boundaries, and three validator/validator-set storage cases. Storage uses +`grovedb-bincode` 2.1.0 with `standard().with_big_endian()` and DPP's JSON schema. + +The scalar and infinity cases characterize **historical compatibility**, not +recommended validation for a new protocol. DPP accepts nonzero scalars reduced +modulo the group order and permits an identity public key to deserialize; an +identity signature still fails verification. Replacing those rules requires a +separate protocol decision. The strict dash-pkc key constructors differ here. + +The frozen expected values come from the original DPP backend listed above. +The generator calls the current DPP implementation; after a backend change, +compare its output with the frozen fixture rather than replacing the fixture. +To inspect its output, run: + +```sh +cargo run -p dpp --example generate_bls_compatibility_vectors --locked +cargo test -p dpp --test bls_compatibility --locked +``` + +Keep the frozen fixture unchanged when migrating the implementation. These tests +cover the listed cases, not an exhaustive proof of equivalence or every persisted +Platform structure. They do not test legacy signature hashing; the legacy field +is the public-key encoding of the same point. diff --git a/packages/rs-dpp/tests/fixtures/bls_compatibility/vectors.json b/packages/rs-dpp/tests/fixtures/bls_compatibility/vectors.json new file mode 100644 index 00000000000..f6c1d09c051 --- /dev/null +++ b/packages/rs-dpp/tests/fixtures/bls_compatibility/vectors.json @@ -0,0 +1,328 @@ +{ + "basic": [ + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000001", + "message": "", + "public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "a8aab303e33ed14f4a904004a92bd26ffc969c1d1e7d4b7f0c04150a73e1845a911e51a2b2d369d5cef06560c5ac9f5715c01566993d4469805df3e1f29b536481a832bf2751b6908faed6776d062d585521889232999d72b679d6e38bb5cfff" + }, + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000001", + "message": "4461736820506c6174666f726d20424c5320636f6d7061746962696c697479", + "public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "885957c480db7a90ba694ab40ea93e30e7e74c95e4ca3be0a67cda09e6f29b0d3b82d4bf9f56d6d6c2d410ed57f060b812569fac5b13555b4bb799ee33b5efb3024480e84d68962ff5db13b156d05f65d56fceec528f8bb8f1d740484d7a72e0" + }, + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000001", + "message": "0000000000000000000000000000000000000000000000000000000000000000", + "public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "b72f4d8dfacf216719bcebb8349312f6bce892587291ee672fc0c7b6d16f427002713a961d122633e2deda35e803e9c302b3fcbc9d36fbb5fdb644bd41ce296399a5ea115f0c6cd612ff7e8b08477993cf1a2412fd81bc07e7ecc7380cfd2876" + }, + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000001", + "message": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "8a527aa6d18e63de70f31c512406217224c99f5a087fd7bcea48737d40ab99bcff2ab6b20b197c1e16efaa9c3dceeb7b01be7de81f980049e69395369a96f994b18f8358f8ce52675dfd8262f69e738eab510a5bba303756955c7a7667b4b477" + }, + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000002", + "message": "", + "public_key": "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "legacy_public_key": "8572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "signature": "a17a941aa5c8600ed1558ec999ed9708a164624c01160a4a1f467bbff22df6db13bf07760b59d1c59f90a3d7e518e0100d65cdf14582f3b85176dd7b76960a655479e2d0a187ef3cf334051dbe0aae7fa1a0cf26297d7a221013e55dd0c0741d" + }, + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000002", + "message": "4461736820506c6174666f726d20424c5320636f6d7061746962696c697479", + "public_key": "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "legacy_public_key": "8572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "signature": "843df9b31366b4bee0b8cc9bacb9f2905b9fd87ce2df1f6a5bfd77ee0a67f3f851dbae7866aca20d0bdac74c5d1d1042089cb625364a87bafc81c4e9e582fb6d5400e2d350f6da811f35f647402597f8fc28ec4f4fcf1d9c611f4a5044b4b4ca" + }, + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000002", + "message": "0000000000000000000000000000000000000000000000000000000000000000", + "public_key": "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "legacy_public_key": "8572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "signature": "8be4ae3fc1c613d49655bcedd5945cc2f4eaf38122f4dfa50f38153b0461425a743b9b3fe8d175d0a41c725bea12ba05035c1c95c85ae567c6f5cd3c6a5975b17a67683dbe2eb70897e9b018e80ac123ec6bb843a614f277851fccc548a2f695" + }, + { + "secret_key": "0000000000000000000000000000000000000000000000000000000000000002", + "message": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "public_key": "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "legacy_public_key": "8572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "signature": "ac997b406ecf999caa44ca3a0e2b42ce77bc6782fc8ac02c54e59d723e128f24f0a1f88a71b21f9890a3282a76632aa1061d736d3d502d81a2a9a1c863752a372ae94a64115d12f2ff34362861e4bb768ad533d0c087906485e795abda688f8d" + }, + { + "secret_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "message": "", + "public_key": "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "legacy_public_key": "15fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "signature": "a01aa8ac3da427ac661695badf9bb4ef2031add7eda51556349cc30b75a2a7de800d4b18a36bad836f576f30225f52a40abc60004dd0e6a834caed027bb25c674181debcbe5d072d8f8758be29887a5a99deef10c04afaf3c3cefe65363636b4" + }, + { + "secret_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "message": "4461736820506c6174666f726d20424c5320636f6d7061746962696c697479", + "public_key": "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "legacy_public_key": "15fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "signature": "8b1b6663cd08c760736bd95d4196d6db75a50b87b639228f9fffa5b7bfa0f43ca29f1f4be87b44fd395f8afe3804b85b068cde11d73f3fed9b05b9c9f296a474ac23f437c85c3f1dc973262b19b02c6875ce2bd911d3ebb3b639aaef61f6a3ae" + }, + { + "secret_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "message": "0000000000000000000000000000000000000000000000000000000000000000", + "public_key": "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "legacy_public_key": "15fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "signature": "af47ffcd8490a3d28c82c6b8c050571ed3eef9b561d9889bce097bd1d0cffbde17b6d9e7aa0e1f2415b1f8bb7e07467614c4d45fae16611b45f9e3d763ab2192ed723f3ea01b146631e68f08cbb1c20ac09ef80dbc0145d9c805e4b4b9771acc" + }, + { + "secret_key": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "message": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "public_key": "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "legacy_public_key": "15fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "signature": "b7921607432c421102b45efd5c1c345b329b61ebaabd6efc99d65971a4b68e358f5f1b341b16cf56b451e7b424f53525172ea4279a3276a6d365d04e9eeb5503e1edb8ad467739d6dd7b82811324842d66088e5c211a3210acfa7cfc939441e6" + }, + { + "secret_key": "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000", + "message": "", + "public_key": "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "88aab303e33ed14f4a904004a92bd26ffc969c1d1e7d4b7f0c04150a73e1845a911e51a2b2d369d5cef06560c5ac9f5715c01566993d4469805df3e1f29b536481a832bf2751b6908faed6776d062d585521889232999d72b679d6e38bb5cfff" + }, + { + "secret_key": "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000", + "message": "4461736820506c6174666f726d20424c5320636f6d7061746962696c697479", + "public_key": "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "a85957c480db7a90ba694ab40ea93e30e7e74c95e4ca3be0a67cda09e6f29b0d3b82d4bf9f56d6d6c2d410ed57f060b812569fac5b13555b4bb799ee33b5efb3024480e84d68962ff5db13b156d05f65d56fceec528f8bb8f1d740484d7a72e0" + }, + { + "secret_key": "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000", + "message": "0000000000000000000000000000000000000000000000000000000000000000", + "public_key": "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "972f4d8dfacf216719bcebb8349312f6bce892587291ee672fc0c7b6d16f427002713a961d122633e2deda35e803e9c302b3fcbc9d36fbb5fdb644bd41ce296399a5ea115f0c6cd612ff7e8b08477993cf1a2412fd81bc07e7ecc7380cfd2876" + }, + { + "secret_key": "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000", + "message": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "public_key": "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "legacy_public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "aa527aa6d18e63de70f31c512406217224c99f5a087fd7bcea48737d40ab99bcff2ab6b20b197c1e16efaa9c3dceeb7b01be7de81f980049e69395369a96f994b18f8358f8ce52675dfd8262f69e738eab510a5bba303756955c7a7667b4b477" + } + ], + "secure_aggregation": [ + { + "secret_keys": [ + "0000000000000000000000000000000000000000000000000000000000000001" + ], + "message": "4461736820506c6174666f726d20736563757265206167677265676174696f6e", + "public_keys": [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + ], + "signature": "a5b6cbc5a885310da739d03370f1ee5d71da922c1c4fbb9a340cae955f25c78fb773bada0601caaadfbeb7c8e9805a860012dae1841d8ad9266444fff8fd0c4783ba46d86d0844c099a5d87c9edf22c50d6b82b18f6ea8499e0d711238504668" + }, + { + "secret_keys": [ + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002" + ], + "message": "4461736820506c6174666f726d20736563757265206167677265676174696f6e", + "public_keys": [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e" + ], + "signature": "8921aa203d93e45302b404bc523295cc80d7d691b4f0a8081a1580752dbf68ea0acf681c52a74520d5c42a5739e6452102c928a29684b67eee818bf211bb019c9d3a7221975c0d1aa3b278bb3c6b86032b2d25c077b167907bc9ca6927bcab94" + }, + { + "secret_keys": [ + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + ], + "message": "4461736820506c6174666f726d20736563757265206167677265676174696f6e", + "public_keys": [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48" + ], + "signature": "8c456b1677371cd2648677f66761539fb5d9f09b8c1a3587f4b2c95f737d9e3c509e7f293aad71322a7156b7d4581e54110c58f8055863344bea1a3e51700d1e6d30605cbab292590cae730e375dc00e60d049c9ee726b315d9c81328d03bd5f" + }, + { + "secret_keys": [ + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000002" + ], + "message": "4461736820506c6174666f726d20736563757265206167677265676174696f6e", + "public_keys": [ + "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e" + ], + "signature": "8c456b1677371cd2648677f66761539fb5d9f09b8c1a3587f4b2c95f737d9e3c509e7f293aad71322a7156b7d4581e54110c58f8055863344bea1a3e51700d1e6d30605cbab292590cae730e375dc00e60d049c9ee726b315d9c81328d03bd5f" + }, + { + "secret_keys": [ + "0000000000000000000000000000000000000000000000000000000000000001", + "0000000000000000000000000000000000000000000000000000000000000001" + ], + "message": "4461736820506c6174666f726d20736563757265206167677265676174696f6e", + "public_keys": [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + ], + "signature": "a71e6905eb7fecd7fdf91a794a0140676c8e2f94aa5420a295c668d529b7e5306478a32cf9f50c230a94f9626d893e8304bb21490ee8c7471e893c632dae250c6a49a9efb8cd5c0e790e884516b41f4a0fb5c45804d1d7e56bb366f2e153973c" + } + ], + "scalars": [ + { + "name": "zero", + "input": "0000000000000000000000000000000000000000000000000000000000000000", + "normalized": null, + "public_key": null, + "signature": null + }, + { + "name": "order", + "input": "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + "normalized": null, + "public_key": null, + "signature": null + }, + { + "name": "order_plus_one", + "input": "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000002", + "normalized": "0000000000000000000000000000000000000000000000000000000000000001", + "public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "signature": "b4ae0ce826b35665b1419394a212f47d9583fa453fd5096a655476cff885f8ad94418c7b9980d6b91c86b23a47a3beb510f6ceb0768fbee0bac279512d9627fe72d4d48d8a3efbef8f7652dd90836104c45cc9a680c82025c68eba5785b4c882" + }, + { + "name": "max", + "input": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "normalized": "1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffd", + "public_key": "96ea601ca88f7d3489479129b258960b4c1df37194d30803627c30c34252679a0ada1a51bc7a4006a4f0564050d31746", + "signature": "aeab9eed96af89a7f8a8d0103496b59c950368c76d0bb18e4d57df607b24f75e80f8a436871f7cf9bb6bbf3a81e0939317e5e56648c0ba4958102649229b8dbed297a9fd29e49c13e138e181ffe7f55062597c0e15cdb42a63fe48c12b4ccf0c" + } + ], + "storage": [ + { + "name": "public_key", + "validator": { + "pro_tx_hash": "1111111111111111111111111111111111111111111111111111111111111111", + "public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "node_ip": "127.0.0.1", + "node_id": "2222222222222222222222222222222222222222", + "core_port": 19999, + "platform_http_port": 1443, + "platform_p2p_port": 26656, + "is_banned": false + }, + "validator_bytes": "11111111111111111111111111111111111111111111111111111111111111110197f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb093132372e302e302e312222222222222222222222222222222222222222fb4e1ffb05a3fb682000", + "validator_set": { + "quorum_hash": "3333333333333333333333333333333333333333333333333333333333333333", + "quorum_index": 2, + "core_height": 123456, + "members": { + "1111111111111111111111111111111111111111111111111111111111111111": { + "pro_tx_hash": "1111111111111111111111111111111111111111111111111111111111111111", + "public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "node_ip": "127.0.0.1", + "node_id": "2222222222222222222222222222222222222222", + "core_port": 19999, + "platform_http_port": 1443, + "platform_p2p_port": 26656, + "is_banned": false + } + }, + "threshold_public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + }, + "validator_set_bytes": "33333333333333333333333333333333333333333333333333333333333333330102fc0001e2400120111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110197f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb093132372e302e302e312222222222222222222222222222222222222222fb4e1ffb05a3fb68200097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + }, + { + "name": "absent", + "validator": { + "pro_tx_hash": "1111111111111111111111111111111111111111111111111111111111111111", + "public_key": null, + "node_ip": "127.0.0.1", + "node_id": "2222222222222222222222222222222222222222", + "core_port": 19999, + "platform_http_port": 1443, + "platform_p2p_port": 26656, + "is_banned": false + }, + "validator_bytes": "111111111111111111111111111111111111111111111111111111111111111100093132372e302e302e312222222222222222222222222222222222222222fb4e1ffb05a3fb682000", + "validator_set": { + "quorum_hash": "3333333333333333333333333333333333333333333333333333333333333333", + "quorum_index": 2, + "core_height": 123456, + "members": { + "1111111111111111111111111111111111111111111111111111111111111111": { + "pro_tx_hash": "1111111111111111111111111111111111111111111111111111111111111111", + "public_key": null, + "node_ip": "127.0.0.1", + "node_id": "2222222222222222222222222222222222222222", + "core_port": 19999, + "platform_http_port": 1443, + "platform_p2p_port": 26656, + "is_banned": false + } + }, + "threshold_public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + }, + "validator_set_bytes": "33333333333333333333333333333333333333333333333333333333333333330102fc0001e24001201111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100093132372e302e302e312222222222222222222222222222222222222222fb4e1ffb05a3fb68200097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + }, + { + "name": "infinity", + "validator": { + "pro_tx_hash": "1111111111111111111111111111111111111111111111111111111111111111", + "public_key": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "node_ip": "127.0.0.1", + "node_id": "2222222222222222222222222222222222222222", + "core_port": 19999, + "platform_http_port": 1443, + "platform_p2p_port": 26656, + "is_banned": false + }, + "validator_bytes": "111111111111111111111111111111111111111111111111111111111111111101c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093132372e302e302e312222222222222222222222222222222222222222fb4e1ffb05a3fb682000", + "validator_set": { + "quorum_hash": "3333333333333333333333333333333333333333333333333333333333333333", + "quorum_index": 2, + "core_height": 123456, + "members": { + "1111111111111111111111111111111111111111111111111111111111111111": { + "pro_tx_hash": "1111111111111111111111111111111111111111111111111111111111111111", + "public_key": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "node_ip": "127.0.0.1", + "node_id": "2222222222222222222222222222222222222222", + "core_port": 19999, + "platform_http_port": 1443, + "platform_p2p_port": 26656, + "is_banned": false + } + }, + "threshold_public_key": "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + }, + "validator_set_bytes": "33333333333333333333333333333333333333333333333333333333333333330102fc0001e24001201111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093132372e302e302e312222222222222222222222222222222222222222fb4e1ffb05a3fb68200097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + } + ], + "key_generation": [ + { + "ikm": "0000000000000000000000000000000000000000000000000000000000000000", + "secret_key": "4a353be3dac091a0a7e640620372f5e1e2e4401717c1e79cac6ffba8f6905604", + "public_key": "85695fcbc06cc4c4c9451f4dce21cbf8de3e5a13bf48f44cdbb18e2038ba7b8bb1632d7911ef1e2e08749bddbf165352" + }, + { + "ikm": "0101010101010101010101010101010101010101010101010101010101010101", + "secret_key": "6fc9d9a2b05fd1f0e51bc91041a03be8657081f272ec281aff731624f0d1c220", + "public_key": "aefe1789d6476f60439e1168f588ea16652dc321279f05a805fbc63933e88ae9c175d6c6ab182e54af562e1a0dce41bb" + }, + { + "ikm": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "secret_key": "4ecb8197b8a2af28bfadf88f8084662c078bed1fa6970fd89b95ed541e35b3be", + "public_key": "96a5412d094be0d2017da0b7f17fe32540ad8356e59d181aec873543cc01433cf8cb251d3e1f950adfeeca9075a7e89c" + } + ] +} diff --git a/packages/rs-dpp/tests/fixtures/historical_hashes/README.md b/packages/rs-dpp/tests/fixtures/historical_hashes/README.md new file mode 100644 index 00000000000..e0d909c43d4 --- /dev/null +++ b/packages/rs-dpp/tests/fixtures/historical_hashes/README.md @@ -0,0 +1,39 @@ +# Historical hash and signature vectors + +The constants freeze outputs of Platform +`6499c680c6bc311e8933f396423a79459e2a88bd` (the base before #5307): rust-dashcore +`40268cc0402a8933ec539f16b2d634c4e25876ad`, secp256k1 0.30.0 and blsful +`0c34a7a488a0bd1c9a9a2196e793b303ad35c900`. + +`core_transactions.rs` holds eight consensus-encoded transactions and their txids: +regular/Evo registration, registrar/service/revocation updates, and coinbase +payload versions 1, 2 and 3. They are synthetic encoding examples; their byte-pattern +BLS fields are not valid signatures. Copy `generate-core-transactions.rs` to the +old DPP examples directory as `generate_core_transactions.rs`, then run: + +```sh +cargo run -p dpp --example generate_core_transactions --locked -- /path/to/output +``` + +`vectors.rs` holds actual signing inputs, hashes and signatures: + +- a two-input address transfer, including ECDSA and Basic BLS signatures; +- InstantLock and ChainLock consensus bytes, request IDs and sign IDs; +- a synthetic DashPay contactRequest batch containing historical ciphertexts. + +For reproduction, first run +`packages/rs-platform-encryption/tests/fixtures/generate-historical-encryption.rs` +as an example in the old encryption crate. This emits `encrypted-xpub.bin` and +`encrypted-label.bin`. Copy `generate.rs` here to the old DPP examples directory +as `generate_historical_hashes.rs`, then run it with the same output directory: + +```sh +cargo run -p dpp --example generate_historical_hashes --locked -- /path/to/output +``` + +Compare the hex outputs with the constants; do not refresh expectations using +the current dependencies. Generators live below `tests/fixtures` so the current +build does not compile their historical APIs. All keys and scalars are public +test material. Ciphertext construction is covered separately by +`platform-encryption/tests/historical_compatibility.rs`; the DPP test covers its +inclusion in the signed batch, not contract validation or encryption policy. diff --git a/packages/rs-dpp/tests/fixtures/historical_hashes/core_transactions.rs b/packages/rs-dpp/tests/fixtures/historical_hashes/core_transactions.rs new file mode 100644 index 00000000000..70346044c71 --- /dev/null +++ b/packages/rs-dpp/tests/fixtures/historical_hashes/core_transactions.rs @@ -0,0 +1,58 @@ +//! Consensus bytes and txids generated with rust-dashcore +//! 40268cc0402a8933ec539f16b2d634c4e25876ad (Platform's pre-upgrade pin). +//! Synthetic public payloads: this checks hashing/encoding, not consensus validity. + +pub const TRANSACTIONS: &[(&str, &str, &str)] = &[ + ("registration-evo", concat!( + "0300010000010001d20400000000000001512a000000fd12010200010000000101010101010101010101010101010101", + "0101010101010101010101010101010300000000000000000000000000ffff7f0000014e1f0202020202020202020202", + "020202020202020202000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526", + "2728292a2b2c2d2e2f030303030303030303030303030303030303030364000151040404040404040404040404040404", + "040404040404040404040404040404040414131211100f0e0d0c0b0a0908070605040302012068bb0141050505050505", + "050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505", + "0505050505050505050505", + ), "a30b4ff60dd0d633f4de56c3a1d8ee6fa875c2bba59a4cf192db5e8917e4e549"), + ("registration-regular", concat!( + "0300010000010001d20400000000000001512a000000fa02000000000001010101010101010101010101010101010101", + "010101010101010101010101010300000000000000000000000000ffff7f0000014e1f02020202020202020202020202", + "02020202020202000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728", + "292a2b2c2d2e2f0303030303030303030303030303030303030303640001510404040404040404040404040404040404", + "040404040404040404040404040404410505050505050505050505050505050505050505050505050505050505050505", + "050505050505050505050505050505050505050505050505050505050505050505", + ), "d1698b27545732719b89868a83e86a310c26f97fee2e8de682de493e0e9c90dc"), + ("registrar", concat!( + "0300030000010001d20400000000000001512a000000cc02000101010101010101010101010101010101010101010101", + "0101010101010101010000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324", + "25262728292a2b2c2d2e2f03030303030303030303030303030303030303030151040404040404040404040404040404", + "040404040404040404040404040404040441050505050505050505050505050505050505050505050505050505050505", + "0505050505050505050505050505050505050505050505050505050505050505050505", + ), "63162c82b3b22c52104f886f91e1f2b932a6ac0e464ccffc5904ed4488881e25"), + ("service-evo", concat!( + "0300020000010001d20400000000000001512a000000d002000100010101010101010101010101010101010101010101", + "0101010101010101010101010000000000000000000000000000004e1f01510404040404040404040404040404040404", + "04040404040404040404040404040414131211100f0e0d0c0b0a0908070605040302012068bb01060606060606060606", + "060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606", + "060606060606060606060606060606060606060606060606060606060606060606060606060606", + ), "466792522ea10d3ff80bb54dad7287987ec9da655df69221ff9e394b46f7cab2"), + ("revocation", concat!( + "0300040000010001d20400000000000001512a000000a402000101010101010101010101010101010101010101010101", + "010101010101010101010004040404040404040404040404040404040404040404040404040404040404040606060606", + "060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606", + "06060606060606060606060606060606060606060606060606060606060606060606060606060606060606", + ), "afe38deff52be3642bf85cdd803495d40add0910c2042e055cbd6742249ba180"), + ("coinbase-v1", concat!( + "030005000001d20400000000000001512a0000002601007b000000080808080808080808080808080808080808080808", + "0808080808080808080808", + ), "aec1210e737889ac3bd1c2a8c69416fb94b844710867ea992386d17a0f7b3e6f"), + ("coinbase-v2", concat!( + "030005000001d20400000000000001512a0000004602007b000000080808080808080808080808080808080808080808", + "08080808080808080808080909090909090909090909090909090909090909090909090909090909090909", + ), "085c1e04b82ee7f1aaae1710153c27abc887a747a2a0c2a1962cee3b109bd534"), + ("coinbase-v3", concat!( + "030005000001d20400000000000001512a000000af03007b000000080808080808080808080808080808080808080808", + "080808080808080808080809090909090909090909090909090909090909090909090909090909090909096406060606", + "060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606", + "060606060606060606060606060606060606060606060606060606060606060606060606060606060606060688130000", + "00000000", + ), "b6ea50bbf7a4309887b5d13130bae1d74fe3485dcbd13392882f3a1d82979528"), +]; diff --git a/packages/rs-dpp/tests/fixtures/historical_hashes/generate-core-transactions.rs b/packages/rs-dpp/tests/fixtures/historical_hashes/generate-core-transactions.rs new file mode 100644 index 00000000000..5c69c6e0b5d --- /dev/null +++ b/packages/rs-dpp/tests/fixtures/historical_hashes/generate-core-transactions.rs @@ -0,0 +1,140 @@ +//! Run in a standalone package with dashcore at rev +//! 40268cc0402a8933ec539f16b2d634c4e25876ad (feature serde). +//! `cargo run --locked -- /path/to/output`; copy hex-encoded consensus bytes +//! and txids into core_transactions.rs. Never refresh with the upgraded pin. +//! Run against rust-dashcore 40268cc0402a8933ec539f16b2d634c4e25876ad. +use dashcore::bls_sig_utils::{BLSPublicKey, BLSSignature}; +use dashcore::hashes::Hash; +use dashcore::transaction::special_transaction::{ + provider_registration::{ProviderMasternodeType, ProviderRegistrationPayload}, + provider_update_registrar::ProviderUpdateRegistrarPayload, + provider_update_revocation::ProviderUpdateRevocationPayload, + provider_update_service::ProviderUpdateServicePayload, + TransactionPayload, +}; +use dashcore::{OutPoint, PlatformNodeId, PubkeyHash, ScriptBuf, Transaction, TxOut, Txid}; + +fn main() { + let output = std::path::PathBuf::from(std::env::args().nth(1).expect("fixture directory")); + let key = BLSPublicKey::from(std::array::from_fn(|i| i as u8)); + let node = PlatformNodeId::from_byte_array(std::array::from_fn(|i| i as u8 + 1)); + let registration = ProviderRegistrationPayload { + version: 2, + masternode_type: ProviderMasternodeType::HighPerformance, + masternode_mode: 0, + collateral_outpoint: OutPoint { + txid: Txid::from_byte_array([1; 32]), + vout: 3, + }, + service_address: "127.0.0.1:19999".parse().unwrap(), + owner_key_hash: PubkeyHash::from_byte_array([2; 20]), + operator_public_key: key, + voting_key_hash: PubkeyHash::from_byte_array([3; 20]), + operator_reward: 100, + script_payout: ScriptBuf::from(vec![0x51]), + inputs_hash: dashcore::hash_types::InputsHash::from_byte_array([4; 32]), + signature: vec![5; 65], + platform_node_id: Some(node), + platform_p2p_port: Some(26656), + platform_http_port: Some(443), + }; + let mut regular = registration.clone(); + regular.masternode_type = ProviderMasternodeType::Regular; + regular.platform_node_id = None; + regular.platform_p2p_port = None; + regular.platform_http_port = None; + let mut records = vec![ + ( + "registration-evo", + TransactionPayload::ProviderRegistrationPayloadType(registration), + ), + ( + "registration-regular", + TransactionPayload::ProviderRegistrationPayloadType(regular), + ), + ( + "registrar", + TransactionPayload::ProviderUpdateRegistrarPayloadType( + ProviderUpdateRegistrarPayload { + version: 2, + pro_tx_hash: Txid::from_byte_array([1; 32]), + provider_mode: 0, + operator_public_key: key, + voting_key_hash: PubkeyHash::from_byte_array([3; 20]), + script_payout: ScriptBuf::from(vec![0x51]), + inputs_hash: dashcore::hash_types::InputsHash::from_byte_array([4; 32]), + payload_sig: vec![5; 65], + }, + ), + ), + ( + "service-evo", + TransactionPayload::ProviderUpdateServicePayloadType(ProviderUpdateServicePayload { + version: 2, + mn_type: Some(1), + pro_tx_hash: Txid::from_byte_array([1; 32]), + ip_address: 1, + port: 19999, + script_payout: ScriptBuf::from(vec![0x51]), + inputs_hash: dashcore::hash_types::InputsHash::from_byte_array([4; 32]), + platform_node_id: Some(node), + platform_p2p_port: Some(26656), + platform_http_port: Some(443), + payload_sig: BLSSignature::from([6; 96]), + }), + ), + ( + "revocation", + TransactionPayload::ProviderUpdateRevocationPayloadType( + ProviderUpdateRevocationPayload { + version: 2, + pro_tx_hash: Txid::from_byte_array([1; 32]), + reason: 1, + inputs_hash: dashcore::hash_types::InputsHash::from_byte_array([4; 32]), + payload_sig: BLSSignature::from([6; 96]), + }, + ), + ), + ]; + for (name, version) in [("coinbase-v1", 1), ("coinbase-v2", 2), ("coinbase-v3", 3)] { + records.push(( + name, + TransactionPayload::CoinbasePayloadType( + dashcore::transaction::special_transaction::coinbase::CoinbasePayload { + version, + height: 123, + merkle_root_masternode_list: + dashcore::hash_types::MerkleRootMasternodeList::from_byte_array([8; 32]), + merkle_root_quorums: dashcore::hash_types::MerkleRootQuorums::from_byte_array( + [9; 32], + ), + best_cl_height: (version >= 3).then_some(100), + best_cl_signature: (version >= 3).then_some(BLSSignature::from([6; 96])), + asset_locked_amount: (version >= 3).then_some(5000), + }, + ), + )); + } + for (name, payload) in records { + let transaction = Transaction { + version: 3, + lock_time: 42, + input: vec![], + output: vec![TxOut { + value: 1234, + script_pubkey: ScriptBuf::from(vec![0x51]), + }], + special_transaction_payload: Some(payload), + }; + std::fs::write( + output.join(format!("{name}.consensus")), + dashcore::consensus::serialize(&transaction), + ) + .unwrap(); + std::fs::write( + output.join(format!("{name}.txid")), + transaction.txid().to_string(), + ) + .unwrap(); + } +} diff --git a/packages/rs-dpp/tests/fixtures/historical_hashes/generate.rs b/packages/rs-dpp/tests/fixtures/historical_hashes/generate.rs new file mode 100644 index 00000000000..b01ee905a41 --- /dev/null +++ b/packages/rs-dpp/tests/fixtures/historical_hashes/generate.rs @@ -0,0 +1,166 @@ +//! Run as a DPP example on Platform 6499c680c6bc311e8933f396423a79459e2a88bd. +//! `cargo run -p dpp --example generate_historical_hashes --locked -- /path/to/output` +//! The output directory must contain encrypted-xpub.bin and encrypted-label.bin +//! from platform-encryption's historical generator, run at the same revision. + +use dpp::address_funds::PlatformAddress; +use dpp::dashcore::bls_sig_utils::BLSSignature; +use dpp::dashcore::ephemerealdata::chain_lock::ChainLock; +use dpp::dashcore::hash_types::CycleHash; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use dpp::dashcore::sml::llmq_type::LLMQType; +use dpp::dashcore::{BlockHash, InstantLock, OutPoint, QuorumHash, Txid}; +use dpp::identity::KeyType; +use dpp::native_bls::NativeBlsModule; +use dpp::platform_value::{Identifier, Value}; +use dpp::serialization::{PlatformMessageSignable, PlatformSerializable, Signable}; +use dpp::state_transition::address_funds_transfer_transition::v0::AddressFundsTransferTransitionV0; +use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition; +use dpp::state_transition::batch_transition::document_base_transition::v0::DocumentBaseTransitionV0; +use dpp::state_transition::batch_transition::document_create_transition::{ + v0::DocumentCreateTransitionV0, DocumentCreateTransition, +}; +use dpp::state_transition::batch_transition::{BatchTransition, BatchTransitionV0}; +use dpp::state_transition::StateTransition; +use dpp::util::hash::hash_double; +use dpp::BlsModule; +use std::collections::BTreeMap; +use std::path::Path; + +fn main() { + let dir = std::env::args().nth(1).expect("output directory"); + let dir = Path::new(&dir); + let write = |name: &str, bytes: &[u8]| { + std::fs::write(dir.join(format!("{name}.hex")), hex::encode(bytes)).unwrap(); + }; + let transition: StateTransition = AddressFundsTransferTransitionV0 { + inputs: BTreeMap::from([ + (PlatformAddress::P2pkh([0x11; 20]), (1000, 5000)), + (PlatformAddress::P2sh([0x22; 20]), (251, 7000)), + ]), + outputs: BTreeMap::from([(PlatformAddress::P2pkh([0x33; 20]), 11000)]), + user_fee_increase: 2, + ..Default::default() + } + .into(); + let bytes = transition.signable_bytes().unwrap(); + write("transfer-signable", &bytes); + write("transfer-hash", &hash_double(&bytes)); + write( + "transfer-serialized", + &transition.serialize_to_bytes().unwrap(), + ); + let secret = [1; 32]; + let ecdsa = bytes + .as_slice() + .sign_by_private_key(&secret, KeyType::ECDSA_SECP256K1, &NativeBlsModule) + .unwrap(); + let bls = bytes + .as_slice() + .sign_by_private_key(&secret, KeyType::BLS12_381, &NativeBlsModule) + .unwrap(); + write("transfer-ecdsa-signature", &ecdsa); + write("transfer-bls-signature", &bls); + write( + "ecdsa-public-key", + &PublicKey::from_secret_key(&Secp256k1::new(), &SecretKey::from_slice(&secret).unwrap()) + .serialize(), + ); + write( + "bls-public-key", + &NativeBlsModule.private_key_to_public_key(&secret).unwrap(), + ); + + let encrypted_key = std::fs::read(dir.join("encrypted-xpub.bin")).unwrap(); + let encrypted_label = std::fs::read(dir.join("encrypted-label.bin")).unwrap(); + let create = DocumentCreateTransitionV0 { + base: DocumentBaseTransitionV0 { + id: Identifier::from([0x44; 32]), + identity_contract_nonce: 1, + document_type_name: "contactRequest".into(), + data_contract_id: Identifier::from([0x55; 32]), + } + .into(), + entropy: [0x66; 32], + data: BTreeMap::from([ + ("toUserId".into(), Value::Bytes(vec![0x77; 32])), + ("senderKeyIndex".into(), Value::U32(0)), + ("recipientKeyIndex".into(), Value::U32(1)), + ("accountReference".into(), Value::U32(2)), + ("encryptedPublicKey".into(), Value::Bytes(encrypted_key)), + ( + "encryptedAccountLabel".into(), + Value::Bytes(encrypted_label), + ), + ]), + ..Default::default() + }; + let batch = StateTransition::Batch(BatchTransition::V0(BatchTransitionV0 { + owner_id: Identifier::from([0x33; 32]), + transitions: vec![DocumentTransition::Create(DocumentCreateTransition::V0( + create, + ))], + ..Default::default() + })); + let signable = batch.signable_bytes().unwrap(); + write("dashpay-serialized", &batch.serialize_to_bytes().unwrap()); + write("dashpay-signable", &signable); + write("dashpay-hash", &hash_double(&signable)); + write( + "dashpay-signature", + &signable + .as_slice() + .sign_by_private_key(&secret, KeyType::ECDSA_SECP256K1, &NativeBlsModule) + .unwrap(), + ); + + let signature = BLSSignature::from(std::array::from_fn(|i| i as u8)); + let instant = InstantLock { + version: 1, + inputs: vec![OutPoint { + txid: Txid::from_byte_array(std::array::from_fn(|i| i as u8)), + vout: 1000, + }], + txid: Txid::from_byte_array([0x77; 32]), + cyclehash: CycleHash::from_byte_array([0x88; 32]), + signature, + }; + let chain = ChainLock { + block_height: 1000, + block_hash: BlockHash::from_byte_array([0x99; 32]), + signature, + }; + let quorum_type = LLMQType::from(100u8); + let quorum_hash = QuorumHash::from_byte_array([0x55; 32]); + write( + "instant-lock-consensus", + &dpp::dashcore::consensus::serialize(&instant), + ); + write( + "instant-lock-request-id", + instant.request_id().unwrap().as_byte_array(), + ); + write( + "instant-lock-sign-id", + instant + .sign_id(quorum_type, quorum_hash, None) + .unwrap() + .as_byte_array(), + ); + write( + "chain-lock-consensus", + &dpp::dashcore::consensus::serialize(&chain), + ); + write( + "chain-lock-request-id", + chain.request_id().unwrap().as_byte_array(), + ); + write( + "chain-lock-sign-id", + chain + .sign_id(quorum_type, quorum_hash, None) + .unwrap() + .as_byte_array(), + ); +} diff --git a/packages/rs-dpp/tests/fixtures/historical_hashes/vectors.rs b/packages/rs-dpp/tests/fixtures/historical_hashes/vectors.rs new file mode 100644 index 00000000000..cdb780a64eb --- /dev/null +++ b/packages/rs-dpp/tests/fixtures/historical_hashes/vectors.rs @@ -0,0 +1,95 @@ +//! Generated by generate.rs at Platform 6499c680c6bc311e8933f396423a79459e2a88bd. +//! rust-dashcore 40268cc, blsful 0c34a7a, secp256k1 0.30; all inputs are public test data. + +pub const TRANSFER_SIGNABLE: &str = concat!( + "0c0002001111111111111111111111111111111111111111fb03e8fb1388012222222222222222222222222222222222", + "222222fb00fbfb1b5801003333333333333333333333333333333333333333fb2af80002", +); + +pub const TRANSFER_HASH: &str = "8c083e16f8455d123bbce8b166ca5d6ea77bb12f5f2c362ceadea4248b06a692"; + +pub const TRANSFER_SERIALIZED: &str = concat!( + "0c0002001111111111111111111111111111111111111111fb03e8fb1388012222222222222222222222222222222222", + "222222fb00fbfb1b5801003333333333333333333333333333333333333333fb2af8000200", +); + +pub const TRANSFER_ECDSA_SIGNATURE: &str = concat!( + "1f70b658f25806e339d2dfe598de1a272fdb26d3e7372e531e5bd9cfff86018e6e16ad6cc028b7b56bcaa6cd84077163", + "fc58b134a16a67ed474c62e622b5637f36", +); + +pub const TRANSFER_BLS_SIGNATURE: &str = concat!( + "835710b6206bbd5b32e69f5d69c6254f0172779c906dcbe009bbcbba3fba48410b8eaae6645c1c5407449d1084688ac8", + "15cb01a8cb6cc2d73e222134799af6ae5ce1dbfce5de1148c9010a11bc57d970c48b58aacad1c47e4e8b237dbc0e6c89", +); + +pub const ECDSA_PUBLIC_KEY: &str = + "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"; + +pub const BLS_PUBLIC_KEY: &str = "aa1a1c26055a329817a5759d877a2795f9499b97d6056edde0eea39512f24e8bc874b4471f0501127abb1ea0d9f68ac1"; + +pub const INSTANT_LOCK_CONSENSUS: &str = concat!( + "0101000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fe803000077777777777777777777", + "777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888", + "888888888888000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829", + "2a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f50515253545556575859", + "5a5b5c5d5e5f", +); + +pub const INSTANT_LOCK_REQUEST_ID: &str = + "b8d23d4179eff7049abd9528d5aad75a4d020c4f6e608e1a3b3b22206c8194ad"; + +pub const INSTANT_LOCK_SIGN_ID: &str = + "a44b4447fe4a448bf5dad2a372c04df444e2364f30cc2d790b1da56a126c920d"; + +pub const CHAIN_LOCK_CONSENSUS: &str = concat!( + "e80300009999999999999999999999999999999999999999999999999999999999999999000102030405060708090a0b", + "0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b", + "3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f", +); + +pub const CHAIN_LOCK_REQUEST_ID: &str = + "04021b2a9abbf4baa053efdcad0cc345710d5911f2d87b7e9f2bf316a7898c03"; + +pub const CHAIN_LOCK_SIGN_ID: &str = + "3b15938d1073723af4d090722da31ee3ed9cc744db11179c10cfd09acd151e46"; + +pub const DASHPAY_SERIALIZED: &str = concat!( + "020033333333333333333333333333333333333333333333333333333333333333330100000044444444444444444444", + "44444444444444444444444444444444444444444444010e636f6e746163745265717565737455555555555555555555", + "555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666", + "66666666666606106163636f756e745265666572656e6365040215656e637279707465644163636f756e744c6162656c", + "0a305a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a758680ffe5ebc197cf98b7ea57529729cea6da4cc569079a85c1a7e7a803", + "e06212656e637279707465645075626c69634b65790a605a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a864d1b3807cf80fd27", + "df6cac063a5128fd6119d0d40491a7788cb4e1975bc47e5070c7919e3d8c21ab26be1a763e7908b97cd32a5e36309f3b", + "f9535c519b1b32b2f206696ec6d0e244a2e182fceaa75011726563697069656e744b6579496e64657804010e73656e64", + "65724b6579496e646578040008746f5573657249640a2077777777777777777777777777777777777777777777777777", + "7777777777777700000000", +); + +pub const DASHPAY_SIGNABLE: &str = concat!( + "020033333333333333333333333333333333333333333333333333333333333333330100000044444444444444444444", + "44444444444444444444444444444444444444444444010e636f6e746163745265717565737455555555555555555555", + "555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666", + "66666666666606106163636f756e745265666572656e6365040215656e637279707465644163636f756e744c6162656c", + "0a305a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a758680ffe5ebc197cf98b7ea57529729cea6da4cc569079a85c1a7e7a803", + "e06212656e637279707465645075626c69634b65790a605a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a864d1b3807cf80fd27", + "df6cac063a5128fd6119d0d40491a7788cb4e1975bc47e5070c7919e3d8c21ab26be1a763e7908b97cd32a5e36309f3b", + "f9535c519b1b32b2f206696ec6d0e244a2e182fceaa75011726563697069656e744b6579496e64657804010e73656e64", + "65724b6579496e646578040008746f5573657249640a2077777777777777777777777777777777777777777777777777", + "777777777777770000", +); + +pub const DASHPAY_HASH: &str = "eefce9b0eb85a4ac36ffc04008e0a5dc0b659f2bb306e2d54b24579acdb07cac"; + +pub const DASHPAY_SIGNATURE: &str = concat!( + "1f8de7c406b91a6ddbcc26d04b3be9a1c4593b30935119a928c8578d1739757c8167817b3e32c14072e1c3d62c9fd124", + "b78802d53d46f38b7599c92f80dabbeac2", +); + +pub const ENCRYPTED_XPUB: &str = concat!( + "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a864d1b3807cf80fd27df6cac063a5128fd6119d0d40491a7788cb4e1975bc47e", + "5070c7919e3d8c21ab26be1a763e7908b97cd32a5e36309f3bf9535c519b1b32b2f206696ec6d0e244a2e182fceaa750", +); + +pub const ENCRYPTED_LABEL: &str = "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a758680ffe5ebc197cf98b7ea57529729cea6da4cc569079a85c1a7e7a803e062"; diff --git a/packages/rs-dpp/tests/historical_hash_compatibility.rs b/packages/rs-dpp/tests/historical_hash_compatibility.rs new file mode 100644 index 00000000000..e983be0ab01 --- /dev/null +++ b/packages/rs-dpp/tests/historical_hash_compatibility.rs @@ -0,0 +1,150 @@ +#[path = "fixtures/historical_hashes/core_transactions.rs"] +mod core_transactions; +#[path = "fixtures/historical_hashes/vectors.rs"] +mod vectors; + +use dpp::dashcore::consensus::{deserialize, serialize}; +use dpp::dashcore::ephemerealdata::chain_lock::ChainLock; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::sml::llmq_type::LLMQType; +use dpp::dashcore::{InstantLock, QuorumHash, Transaction}; +use dpp::identity::KeyType; +use dpp::native_bls::NativeBlsModule; +use dpp::platform_value::Value; +use dpp::serialization::{ + PlatformDeserializableUntrusted, PlatformMessageSignable, PlatformSerializable, Signable, +}; +use dpp::state_transition::batch_transition::batched_transition::document_transition::DocumentTransition; +use dpp::state_transition::batch_transition::document_create_transition::DocumentCreateTransition; +use dpp::state_transition::batch_transition::BatchTransition; +use dpp::state_transition::StateTransition; +use dpp::util::hash::hash_double; +use vectors::*; + +#[test] +fn should_preserve_historical_core_transaction_bytes_and_txids() { + for (name, consensus, txid) in core_transactions::TRANSACTIONS { + let bytes = hex::decode(consensus).unwrap(); + let transaction: Transaction = deserialize(&bytes).unwrap(); + assert_eq!(serialize(&transaction), bytes, "{name}"); + assert_eq!(transaction.txid().to_string(), *txid, "{name}"); + } +} + +#[test] +fn should_preserve_historical_lock_request_and_sign_ids() { + let instant_bytes = hex::decode(INSTANT_LOCK_CONSENSUS).unwrap(); + let chain_bytes = hex::decode(CHAIN_LOCK_CONSENSUS).unwrap(); + let instant: InstantLock = deserialize(&instant_bytes).unwrap(); + let chain: ChainLock = deserialize(&chain_bytes).unwrap(); + let quorum_type = LLMQType::from(100u8); + let quorum_hash = QuorumHash::from_byte_array([0x55; 32]); + assert_eq!(serialize(&instant), instant_bytes); + assert_eq!(serialize(&chain), chain_bytes); + assert_eq!( + hex::encode(instant.request_id().unwrap()), + INSTANT_LOCK_REQUEST_ID + ); + assert_eq!( + hex::encode(chain.request_id().unwrap()), + CHAIN_LOCK_REQUEST_ID + ); + assert_eq!( + hex::encode(instant.sign_id(quorum_type, quorum_hash, None).unwrap()), + INSTANT_LOCK_SIGN_ID + ); + assert_eq!( + hex::encode(chain.sign_id(quorum_type, quorum_hash, None).unwrap()), + CHAIN_LOCK_SIGN_ID + ); +} + +#[test] +fn should_preserve_historical_transfer_preimage_hash_and_signatures() { + let serialized = hex::decode(TRANSFER_SERIALIZED).unwrap(); + let transition = StateTransition::deserialize_from_bytes_untrusted_exact(&serialized).unwrap(); + assert_eq!(transition.serialize_to_bytes().unwrap(), serialized); + let bytes = transition.signable_bytes().unwrap(); + assert_eq!(hex::encode(&bytes), TRANSFER_SIGNABLE); + assert_eq!(hex::encode(hash_double(&bytes)), TRANSFER_HASH); + for (key_type, public, signature) in [ + ( + KeyType::ECDSA_SECP256K1, + ECDSA_PUBLIC_KEY, + TRANSFER_ECDSA_SIGNATURE, + ), + (KeyType::BLS12_381, BLS_PUBLIC_KEY, TRANSFER_BLS_SIGNATURE), + ] { + let public = hex::decode(public).unwrap(); + let signature = hex::decode(signature).unwrap(); + assert_eq!( + bytes + .as_slice() + .sign_by_private_key(&[1; 32], key_type, &NativeBlsModule) + .unwrap(), + signature + ); + assert!(bytes + .as_slice() + .verify_signature(key_type, &public, &signature) + .is_valid()); + let mut tampered = bytes.clone(); + tampered[0] ^= 1; + assert!(!tampered + .as_slice() + .verify_signature(key_type, &public, &signature) + .is_valid()); + } +} + +#[test] +fn should_preserve_historical_dashpay_ciphertexts_in_the_signed_batch() { + let serialized = hex::decode(DASHPAY_SERIALIZED).unwrap(); + let mut transition = + StateTransition::deserialize_from_bytes_untrusted_exact(&serialized).unwrap(); + assert_eq!(transition.serialize_to_bytes().unwrap(), serialized); + let bytes = transition.signable_bytes().unwrap(); + assert_eq!(hex::encode(&bytes), DASHPAY_SIGNABLE); + assert_eq!(hex::encode(hash_double(&bytes)), DASHPAY_HASH); + let signature = hex::decode(DASHPAY_SIGNATURE).unwrap(); + let public = hex::decode(ECDSA_PUBLIC_KEY).unwrap(); + assert_eq!( + bytes + .as_slice() + .sign_by_private_key(&[1; 32], KeyType::ECDSA_SECP256K1, &NativeBlsModule) + .unwrap(), + signature + ); + assert!(bytes + .as_slice() + .verify_signature(KeyType::ECDSA_SECP256K1, &public, &signature) + .is_valid()); + + let StateTransition::Batch(BatchTransition::V0(batch)) = &mut transition else { + panic!("expected a historical document batch"); + }; + let DocumentTransition::Create(DocumentCreateTransition::V0(create)) = + &mut batch.transitions[0] + else { + panic!("expected a historical contactRequest create"); + }; + for (name, frozen) in [ + ("encryptedPublicKey", ENCRYPTED_XPUB), + ("encryptedAccountLabel", ENCRYPTED_LABEL), + ] { + assert_eq!( + create.data.get(name), + Some(&Value::Bytes(hex::decode(frozen).unwrap())) + ); + } + let Value::Bytes(ciphertext) = create.data.get_mut("encryptedPublicKey").unwrap() else { + panic!("expected binary ciphertext"); + }; + ciphertext[16] ^= 1; + let tampered = transition.signable_bytes().unwrap(); + assert_ne!(tampered, bytes); + assert!(!tampered + .as_slice() + .verify_signature(KeyType::ECDSA_SECP256K1, &public, &signature) + .is_valid()); +} diff --git a/packages/rs-drive-abci/src/abci/error.rs b/packages/rs-drive-abci/src/abci/error.rs index 306e0644956..54777e4ce3e 100644 --- a/packages/rs-drive-abci/src/abci/error.rs +++ b/packages/rs-drive-abci/src/abci/error.rs @@ -1,4 +1,4 @@ -use dpp::bls_signatures::BlsError; +use dpp::bls::BlsError; use dpp::consensus::ConsensusError; use tenderdash_abci::proto::abci::ExtendVoteExtension; use tenderdash_abci::proto::types::VoteExtension; diff --git a/packages/rs-drive-abci/src/error/execution.rs b/packages/rs-drive-abci/src/error/execution.rs index 850ddd54717..10645c55941 100644 --- a/packages/rs-drive-abci/src/error/execution.rs +++ b/packages/rs-drive-abci/src/error/execution.rs @@ -1,4 +1,4 @@ -use dpp::bls_signatures::BlsError; +use dpp::bls::BlsError; use dpp::dashcore::consensus::encode::Error as DashCoreConsensusEncodeError; use dpp::identity::TimestampMillis; use dpp::version::FeatureVersion; diff --git a/packages/rs-drive-abci/src/error/mod.rs b/packages/rs-drive-abci/src/error/mod.rs index 241d9e7b37e..8bc9d7c1b6d 100644 --- a/packages/rs-drive-abci/src/error/mod.rs +++ b/packages/rs-drive-abci/src/error/mod.rs @@ -2,7 +2,7 @@ use crate::abci::AbciError; use crate::error::execution::ExecutionError; use crate::error::serialization::SerializationError; use crate::logging; -use dpp::bls_signatures::BlsError; +use dpp::bls::BlsError; use dpp::dashcore_rpc::Error as CoreRpcError; use dpp::data_contract::errors::DataContractError; use dpp::platform_value::Error as ValueError; diff --git a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs index fb6d7e4da69..0004ec39f53 100644 --- a/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/check_tx/v0/mod.rs @@ -296,7 +296,6 @@ mod tests { use simple_signer::signer::SimpleSigner; use dpp::consensus::ConsensusError; - use dpp::dashcore::secp256k1::Secp256k1; use dpp::dashcore::{key::Keypair, signer, Network, PrivateKey}; use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters}; @@ -339,6 +338,8 @@ mod tests { use assert_matches::assert_matches; use dpp::consensus::state::state_error::StateError; use dpp::dash_to_credits; + use dpp::dashcore::secp256k1::rand::rngs::StdRng as SecpStdRng; + use dpp::dashcore::secp256k1::rand::SeedableRng as _; use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Setters; use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use dpp::data_contract::change_control_rules::v0::ChangeControlRulesV0; @@ -3907,15 +3908,13 @@ mod tests { .build_with_mock_rpc() .set_genesis_state(); - let mut rng = StdRng::seed_from_u64(433); + let mut rng = SecpStdRng::seed_from_u64(433); let platform_state = platform.state.load(); let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); - let secp = Secp256k1::new(); - - let new_key_pair = Keypair::new(&secp, &mut rng); + let new_key_pair = Keypair::new(&mut rng); let mut new_key = IdentityPublicKeyInCreationV0 { id: 2, @@ -3933,7 +3932,7 @@ mod tests { .expect("expected to get signable bytes"); let secret = new_key_pair.secret_key(); let signature = - signer::sign(&signable_bytes, &secret.secret_bytes()).expect("expected to sign"); + signer::sign(&signable_bytes, &secret.to_secret_bytes()).expect("expected to sign"); new_key.signature = signature.to_vec().into(); @@ -4011,13 +4010,11 @@ mod tests { let (identity, signer, _, key) = setup_identity_return_master_key(&mut platform, 958, dash_to_credits!(0.1)); - let mut rng = StdRng::seed_from_u64(1); - - let secp = Secp256k1::new(); + let mut rng = SecpStdRng::seed_from_u64(1); let platform_state = platform.state.load(); - let new_key_pair = Keypair::new(&secp, &mut rng); + let new_key_pair = Keypair::new(&mut rng); let new_key = IdentityPublicKeyInCreationV0 { id: 2, diff --git a/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs index 62f1cdfab8c..c611ea2c0a1 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/block_end/validator_set_update/mod.rs @@ -157,7 +157,7 @@ mod tests { use dpp::block::block_info::BlockInfo; use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0; use dpp::block::extended_block_info::ExtendedBlockInfo; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey}; + use dpp::bls::SecretKey; use dpp::core_types::validator::v0::ValidatorV0; use dpp::core_types::validator_set::v0::ValidatorSetV0; use dpp::core_types::validator_set::ValidatorSet; @@ -185,13 +185,13 @@ mod tests { member_seeds: &[u8], rng: &mut StdRng, ) -> ValidatorSet { - let threshold_public_key = SecretKey::::random(&mut *rng).public_key(); + let threshold_public_key = SecretKey::random(&mut *rng).public_key(); let mut members = BTreeMap::new(); for &seed in member_seeds { let mut hash_bytes = [0u8; 32]; hash_bytes[31] = seed; let pro_tx_hash = ProTxHash::from_byte_array(hash_bytes); - let public_key = Some(SecretKey::::random(&mut *rng).public_key()); + let public_key = Some(SecretKey::random(&mut *rng).public_key()); let node_id = PubkeyHash::from_byte_array([seed; 20]); let validator = ValidatorV0 { pro_tx_hash, @@ -407,7 +407,7 @@ mod tests { let mut rng = StdRng::seed_from_u64(45); let qh = quorum_hash_from_seed(1); - let threshold_pk = SecretKey::::random(&mut rng).public_key(); + let threshold_pk = SecretKey::random(&mut rng).public_key(); let empty_vs = ValidatorSet::V0(ValidatorSetV0 { quorum_hash: qh, quorum_index: None, diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_identities/update_operator_identity/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_identities/update_operator_identity/v0/mod.rs index f059b250783..5089b6a5219 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_identities/update_operator_identity/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_identities/update_operator_identity/v0/mod.rs @@ -391,7 +391,7 @@ mod tests { use crate::platform_types::platform_state::PlatformStateV0Methods; use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder}; use dpp::block::block_info::BlockInfo; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey as BlsPrivateKey}; + use dpp::bls::SecretKey as BlsPrivateKey; use dpp::dashcore::hashes::Hash; use dpp::dashcore::ProTxHash; use dpp::dashcore::Txid; @@ -433,11 +433,13 @@ mod tests { .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( - &private_key_operator_bytes.try_into().expect("expected the secret key to be 32 bytes"), + let private_key_operator = BlsPrivateKey::from_be_bytes( + &private_key_operator_bytes + .try_into() + .expect("expected the secret key to be 32 bytes"), ) - .expect("expected the conversion between bls signatures library and blsful to happen without failing"); - let pub_key_operator = private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); let operator_key: IdentityPublicKey = IdentityPublicKeyV0 { id: 0, @@ -976,11 +978,13 @@ mod tests { .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( - &private_key_operator_bytes.try_into().expect("expected the secret key to be 32 bytes"), + let private_key_operator = BlsPrivateKey::from_be_bytes( + &private_key_operator_bytes + .try_into() + .expect("expected the secret key to be 32 bytes"), ) - .expect("expected the conversion between bls signatures library and blsful to happen without failing"); - let new_pub_key_operator = private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let new_pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); // Create an old masternode state let masternode_list_item = MasternodeListItem { @@ -1072,15 +1076,13 @@ mod tests { .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( + let private_key_operator = BlsPrivateKey::from_be_bytes( &private_key_operator_bytes .try_into() .expect("expected the secret key to be 32 bytes"), ) - .expect( - "expected the conversion between bls signatures library and blsful to happen without failing", - ); - let new_pub_key_operator = private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let new_pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); let new_operator_identifier = Identifier::create_operator_identifier( pro_tx_hash.as_byte_array(), @@ -1215,11 +1217,13 @@ mod tests { .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( - &private_key_operator_bytes.try_into().expect("expected the secret key to be 32 bytes"), + let private_key_operator = BlsPrivateKey::from_be_bytes( + &private_key_operator_bytes + .try_into() + .expect("expected the secret key to be 32 bytes"), ) - .expect("expected the conversion between bls signatures library and blsful to happen without failing"); - let new_pub_key_operator = private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let new_pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); // Create an old masternode state with original public key operator let masternode_list_item = MasternodeListItem { diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/mod.rs index 36279aad809..4292296c059 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/mod.rs @@ -92,7 +92,7 @@ mod test { use crate::platform_types::validator_set::{ValidatorSet, ValidatorSetExt}; use crate::rpc::core::MockCoreRPCLike; use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder}; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey}; + use dpp::bls::SecretKey; use dpp::core_types::validator_set::v0::ValidatorSetV0; use dpp::dashcore::hashes::Hash; use dpp::dashcore::{Network, ProTxHash, PubkeyHash, QuorumHash}; @@ -517,10 +517,8 @@ mod test { quorum_index: None, core_height: init_core_height, members: members.clone(), - threshold_public_key: SecretKey::::random( - &mut StdRng::seed_from_u64(1), - ) - .public_key(), + threshold_public_key: SecretKey::random(&mut StdRng::seed_from_u64(1)) + .public_key(), }), ); @@ -893,10 +891,7 @@ mod test { .into_iter() .map(|validator| (validator.pro_tx_hash, validator)) .collect(), - threshold_public_key: SecretKey::::random(&mut StdRng::seed_from_u64( - 1, - )) - .public_key(), + threshold_public_key: SecretKey::random(&mut StdRng::seed_from_u64(1)).public_key(), }) .to_update() .validator_updates @@ -960,10 +955,8 @@ mod test { quorum_index: None, core_height: *core_height, members, - threshold_public_key: SecretKey::::random( - &mut StdRng::seed_from_u64(1), - ) - .public_key(), + threshold_public_key: SecretKey::random(&mut StdRng::seed_from_u64(1)) + .public_key(), }), ); } diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v1/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v1/mod.rs index 996608f515f..af7b64bbd84 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v1/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v1/mod.rs @@ -301,7 +301,7 @@ mod tests { use crate::platform_types::validator_set::{ValidatorSet, ValidatorSetExt}; use crate::rpc::core::MockCoreRPCLike; use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder}; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey}; + use dpp::bls::SecretKey; use dpp::core_types::validator::v0::ValidatorV0; use dpp::core_types::validator_set::v0::ValidatorSetV0; use dpp::dashcore::hashes::Hash; @@ -652,7 +652,7 @@ mod tests { quorum_index: None, core_height: 1, members: BTreeMap::from([(pro_tx_hash, validator)]), - threshold_public_key: SecretKey::::random(&mut rng).public_key(), + threshold_public_key: SecretKey::random(&mut rng).public_key(), }), ); state.mark_saved(); diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs index 2b2b6d1bd29..12fa611b15d 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_quorum_info/v0/mod.rs @@ -16,7 +16,7 @@ use crate::rpc::core::CoreRPCLike; use crate::platform_types::signature_verification_quorum_set::{ SignatureVerificationQuorumSet, SignatureVerificationQuorumSetV0Methods, VerificationQuorum, }; -use dpp::bls_signatures::PublicKey as BlsPublicKey; +use dpp::bls::PublicKey as BlsPublicKey; use dpp::dashcore::QuorumHash; use tracing::Level; diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/mod.rs index 1b0ab684cfe..bc48b50873a 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/mod.rs @@ -2,7 +2,7 @@ mod v0; use crate::error::execution::ExecutionError; use crate::error::Error; -use dpp::bls_signatures::{Bls12381G2Impl, PublicKey as BlsPublicKey}; +use dpp::bls::PublicKey as BlsPublicKey; use dpp::dashcore::QuorumHash; use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType; @@ -24,10 +24,10 @@ where // TODO: use CoreQuorumSet.select_quorums instead pub fn choose_quorum<'a>( llmq_quorum_type: QuorumType, - quorums: &'a BTreeMap>, + quorums: &'a BTreeMap, request_id: &[u8; 32], platform_version: &PlatformVersion, - ) -> Result)>, Error> { + ) -> Result, Error> { match platform_version .drive_abci .methods diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs index 945868ec823..aeebb38a229 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/choose_quorum/v0/mod.rs @@ -1,4 +1,4 @@ -use dpp::bls_signatures::{Bls12381G2Impl, PublicKey as BlsPublicKey}; +use dpp::bls::PublicKey as BlsPublicKey; use dpp::dashcore::hashes::{sha256d, Hash, HashEngine}; use dpp::dashcore::QuorumHash; use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType; @@ -12,15 +12,11 @@ impl Platform { /// Based on DIP8 deterministically chooses a pseudorandom quorum from the list of quorums pub(super) fn choose_quorum_v0<'a>( llmq_quorum_type: QuorumType, - quorums: &'a BTreeMap>, + quorums: &'a BTreeMap, request_id: &[u8; 32], - ) -> Option<(ReversedQuorumHashBytes, &'a BlsPublicKey)> { + ) -> Option<(ReversedQuorumHashBytes, &'a BlsPublicKey)> { // Scoring system logic - let mut scores: Vec<( - ReversedQuorumHashBytes, - &BlsPublicKey, - [u8; 32], - )> = Vec::new(); + let mut scores: Vec<(ReversedQuorumHashBytes, &BlsPublicKey, [u8; 32])> = Vec::new(); for (quorum_hash, public_key) in quorums { let mut quorum_hash_bytes = quorum_hash.to_byte_array().to_vec(); @@ -41,7 +37,8 @@ impl Platform { // Finalize the hash let hash_result = sha256d::Hash::from_engine(hasher); - scores.push((quorum_hash_bytes, public_key, hash_result.into())); + // Scores use raw digest bytes, without reversal, at every protocol version. + scores.push((quorum_hash_bytes, public_key, hash_result.to_byte_array())); } if scores.is_empty() { @@ -83,7 +80,8 @@ impl Platform { // Finalize the hash let hash_result = sha256d::Hash::from_engine(hasher); - scores.push((quorum_hash_bytes, key, hash_result.into())); + // Scores use raw digest bytes, without reversal, at every protocol version. + scores.push((quorum_hash_bytes, key, hash_result.to_byte_array())); } scores.sort_by_key(|k| k.2); @@ -95,7 +93,7 @@ impl Platform { mod tests { use crate::platform_types::platform::Platform; use crate::rpc::core::MockCoreRPCLike; - use dpp::bls_signatures::SecretKey; + use dpp::bls::SecretKey; use dpp::dashcore::hashes::Hash; use dpp::dashcore::QuorumHash; use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType; diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs index 000d46582a2..c924a8f3c74 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs @@ -1,4 +1,4 @@ -use dpp::bls_signatures::{Bls12381G2Impl, Pairing, Signature}; +use dpp::bls::Signature; use dpp::dashcore::hashes::{sha256d, Hash, HashEngine}; use dpp::dashcore::{ChainLock, QuorumSigningRequestId}; @@ -38,11 +38,7 @@ where // First verify that the signature conforms to a signature - let decoded_sig = match ::Signature::from_compressed( - chain_lock.signature.as_bytes(), - ) - .into_option() - { + let signature = match Signature::from_compressed(chain_lock.signature.as_bytes()) { Some(signature) => signature, None => { tracing::error!( @@ -51,16 +47,12 @@ where platform_state.last_committed_block_height() + 1, round ); - return Err(Error::BLSError( - dpp::bls_signatures::BlsError::DeserializationError( - "chain lock signature was not deserializable".to_string(), - ), - )); + return Err(Error::BLSError(dpp::bls::BlsError::DeserializationError( + "chain lock signature was not deserializable".to_string(), + ))); } }; - let signature = Signature::Basic(decoded_sig); - // we attempt to verify the chain lock locally let chain_lock_height = chain_lock.block_height; @@ -237,7 +229,8 @@ where mod tests { use crate::execution::platform_events::core_chain_lock::verify_chain_lock_locally::v0::CHAIN_LOCK_REQUEST_ID_PREFIX; use crate::test::helpers::setup::TestPlatformBuilder; - use dpp::bls_signatures::{Bls12381G2Impl, Pairing}; + use dpp::bls::Signature; + use dpp::dashcore::hashes::{sha256d, Hash, HashEngine}; use dpp::dashcore::{BlockHash, ChainLock, QuorumSigningRequestId}; use dpp::version::PlatformVersion; @@ -327,11 +320,7 @@ mod tests { ]; for signature in signatures { - assert!( - ::Signature::from_compressed(&signature) - .into_option() - .is_some(), - ); + assert!(Signature::from_compressed(&signature).is_some()); } } diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_instant_send_lock/verify_recent_signature_locally/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_instant_send_lock/verify_recent_signature_locally/v0/mod.rs index 2ed186f8a02..dcf4160a1f0 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_instant_send_lock/verify_recent_signature_locally/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_instant_send_lock/verify_recent_signature_locally/v0/mod.rs @@ -1,4 +1,4 @@ -use dpp::bls_signatures::{Bls12381G2Impl, Pairing, Signature}; +use dpp::bls::Signature; use std::fmt::{Debug, Formatter}; use dpp::dashcore::hashes::{sha256d, Hash, HashEngine}; @@ -21,12 +21,8 @@ pub(super) fn verify_recent_instant_lock_signature_locally_v0( ) -> Result { // First verify that the signature conforms to a signature - let signature = match ::Signature::from_compressed( - instant_lock.signature.as_bytes(), - ) - .into_option() - { - Some(signature) => Signature::Basic(signature), + let signature = match Signature::from_compressed(instant_lock.signature.as_bytes()) { + Some(signature) => signature, None => { tracing::trace!( instant_lock = ?InstantLockDebug(instant_lock), "Invalid instant Lock {} signature format", instant_lock.txid, ); diff --git a/packages/rs-drive-abci/src/execution/storage/store_platform_state/v1/mod.rs b/packages/rs-drive-abci/src/execution/storage/store_platform_state/v1/mod.rs index c4919e1fd19..c60fcc245a9 100644 --- a/packages/rs-drive-abci/src/execution/storage/store_platform_state/v1/mod.rs +++ b/packages/rs-drive-abci/src/execution/storage/store_platform_state/v1/mod.rs @@ -180,7 +180,7 @@ mod tests { use crate::rpc::core::MockCoreRPCLike; use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder}; use dpp::bincode::config; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey}; + use dpp::bls::SecretKey; use dpp::core_types::validator::v0::ValidatorV0; use dpp::core_types::validator_set::v0::{ValidatorSetV0, ValidatorSetV0Getters}; use dpp::core_types::validator_set::ValidatorSet; @@ -259,7 +259,7 @@ mod tests { /// threshold key derived from `byte`. fn validator_set(byte: u8, core_height: u32) -> ValidatorSet { let mut rng = StdRng::seed_from_u64(byte as u64); - let threshold_public_key = SecretKey::::random(&mut rng).public_key(); + let threshold_public_key = SecretKey::random(&mut rng).public_key(); let member = ValidatorV0 { pro_tx_hash: pro_tx_hash(byte), public_key: None, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs index 7a002a2c7e8..53664f399df 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funding_from_asset_lock/tests.rs @@ -21,7 +21,6 @@ mod tests { use dpp::consensus::ConsensusError; use dpp::dash_to_credits; use dpp::dashcore::blockdata::script::ScriptBuf; - use dpp::dashcore::secp256k1::Secp256k1; use dpp::dashcore::transaction::special_transaction::asset_lock::AssetLockPayload; use dpp::dashcore::transaction::special_transaction::TransactionPayload; use dpp::dashcore::{BlockHash, Network, OutPoint, PrivateKey, Transaction, TxIn, TxOut, Txid}; @@ -137,7 +136,6 @@ mod tests { use dpp::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; let platform_version = PlatformVersion::latest(); - let secp = Secp256k1::new(); // Generate the one-time key that will receive the asset lock funds let (_, pk) = ECDSA_SECP256K1 @@ -145,7 +143,7 @@ mod tests { .unwrap(); let one_time_private_key = PrivateKey::from_byte_array(&pk, Network::Testnet).unwrap(); - let one_time_public_key = one_time_private_key.public_key(&secp); + let one_time_public_key = one_time_private_key.public_key(); let one_time_key_hash = one_time_public_key.pubkey_hash(); // Create a fake input (doesn't need to be real for our tests) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs index 816a5a5f6d4..26606e65afa 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/direct_selling/mod.rs @@ -9,7 +9,6 @@ mod token_selling_tests { use super::*; use dpp::{ - dashcore::secp256k1::hashes::hex::{Case, DisplayHex}, prelude::{DataContract, Identity, IdentityPublicKey}, tokens::token_pricing_schedule::TokenPricingSchedule, }; @@ -640,7 +639,7 @@ mod token_selling_tests { expected_price, format!( "price in proof mismatch for token {}", - token_id.to_hex_string(Case::Lower) + hex::encode(token_id) ) .as_str(), ); @@ -650,11 +649,7 @@ mod token_selling_tests { fetched_prices.clone(), token_id, expected_price, - format!( - "fetched price mismatch for token {}", - token_id.to_hex_string(Case::Lower) - ) - .as_str(), + format!("fetched price mismatch for token {}", hex::encode(token_id)).as_str(), ); } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/tests.rs index 4e29e5a4936..104123a484c 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/tests.rs @@ -5,7 +5,6 @@ mod tests { use crate::execution::validation::state_transition::state_transitions::test_helpers::{ create_dummy_witness, create_platform_address, setup_address_with_balance, TestAddressSigner, TestHash as Hash, TestPublicKey as PublicKey, - TestSecp256k1 as Secp256k1, }; use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult; use crate::test::helpers::setup::TestPlatformBuilder; @@ -9377,7 +9376,7 @@ mod tests { mod actual_signature_verification { use super::*; use dpp::dashcore::hashes::Hash; - use dpp::dashcore::secp256k1::{PublicKey as RawSecp256k1PublicKey, Secp256k1}; + use dpp::dashcore::secp256k1::PublicKey as RawSecp256k1PublicKey; use dpp::serialization::Signable; /// Helper to create a properly signed P2PKH witness @@ -9386,15 +9385,15 @@ mod tests { secret_key: &dpp::dashcore::secp256k1::SecretKey, signable_bytes: &[u8], ) -> (PlatformAddress, AddressWitness) { - let secp = Secp256k1::new(); - let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(&secp, secret_key); + let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(secret_key); let pubkey = PublicKey::new(raw_pubkey); let pubkey_hash = dpp::dashcore::hashes::hash160::Hash::hash(&pubkey.to_bytes()); let address = PlatformAddress::P2pkh(pubkey_hash.to_byte_array()); // Sign using dashcore::signer which creates a recoverable signature - let signature = dpp::dashcore::signer::sign(signable_bytes, secret_key.as_ref()) - .expect("signing should succeed"); + let signature = + dpp::dashcore::signer::sign(signable_bytes, secret_key.as_secret_bytes()) + .expect("signing should succeed"); let witness = AddressWitness::P2pkh { signature: BinaryData::new(signature.to_vec()), @@ -9411,15 +9410,14 @@ mod tests { let public_keys = create_default_public_keys(&mut rng, platform_version); // Create a real secret key - let secret_key = dpp::dashcore::secp256k1::SecretKey::from_slice(&[ + let secret_key = dpp::dashcore::secp256k1::SecretKey::from_secret_bytes([ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, ]) .expect("valid secret key"); - let secp = Secp256k1::new(); - let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(&secp, &secret_key); + let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(&secret_key); let pubkey = PublicKey::new(raw_pubkey); let pubkey_hash = dpp::dashcore::hashes::hash160::Hash::hash(&pubkey.to_bytes()); let address = PlatformAddress::P2pkh(pubkey_hash.to_byte_array()); @@ -9447,8 +9445,9 @@ mod tests { .expect("should get signable bytes"); // Now create the signature using recoverable signing - let signature = dpp::dashcore::signer::sign(&signable_bytes, secret_key.as_ref()) - .expect("signing should succeed"); + let signature = + dpp::dashcore::signer::sign(&signable_bytes, secret_key.as_secret_bytes()) + .expect("signing should succeed"); // Create the signed transition let signed_transition = IdentityCreateFromAddressesTransition::V0( @@ -9487,17 +9486,16 @@ mod tests { let public_keys = create_default_public_keys(&mut rng, platform_version); // Create address from one key - let correct_secret = dpp::dashcore::secp256k1::SecretKey::from_slice(&[1u8; 32]) + let correct_secret = dpp::dashcore::secp256k1::SecretKey::from_secret_bytes([1u8; 32]) .expect("valid secret key"); - let secp = Secp256k1::new(); - let raw_correct_pubkey = RawSecp256k1PublicKey::from_secret_key(&secp, &correct_secret); + let raw_correct_pubkey = RawSecp256k1PublicKey::from_secret_key(&correct_secret); let correct_pubkey = PublicKey::new(raw_correct_pubkey); let pubkey_hash = dpp::dashcore::hashes::hash160::Hash::hash(&correct_pubkey.to_bytes()); let address = PlatformAddress::P2pkh(pubkey_hash.to_byte_array()); // But sign with different key - let wrong_secret = dpp::dashcore::secp256k1::SecretKey::from_slice(&[2u8; 32]) + let wrong_secret = dpp::dashcore::secp256k1::SecretKey::from_secret_bytes([2u8; 32]) .expect("valid secret key"); let mut inputs = BTreeMap::new(); @@ -9525,7 +9523,7 @@ mod tests { // Sign with WRONG key - this will produce a signature that when recovered // will give a different public key than expected let wrong_signature = - dpp::dashcore::signer::sign(&signable_bytes, wrong_secret.as_ref()) + dpp::dashcore::signer::sign(&signable_bytes, wrong_secret.as_secret_bytes()) .expect("signing should succeed"); // Create transition with mismatched signature (signed by wrong key) @@ -9553,21 +9551,21 @@ mod tests { let mut rng = StdRng::seed_from_u64(6002); let public_keys = create_default_public_keys(&mut rng, platform_version); - let secp = Secp256k1::new(); // Create multiple addresses with their secret keys let secrets: Vec<_> = (1..=3) .map(|i| { let mut key_bytes = [0u8; 32]; key_bytes[0] = i; - dpp::dashcore::secp256k1::SecretKey::from_slice(&key_bytes).expect("valid") + dpp::dashcore::secp256k1::SecretKey::from_secret_bytes(key_bytes) + .expect("valid") }) .collect(); let addresses: Vec<_> = secrets .iter() .map(|secret| { - let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(&secp, secret); + let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(secret); let pubkey = PublicKey::new(raw_pubkey); let pubkey_hash = dpp::dashcore::hashes::hash160::Hash::hash(&pubkey.to_bytes()); @@ -9611,8 +9609,9 @@ mod tests { .position(|a| a == addr) .expect("should find"); let secret = &secrets[idx]; - let signature = dpp::dashcore::signer::sign(&signable_bytes, secret.as_ref()) - .expect("signing should succeed"); + let signature = + dpp::dashcore::signer::sign(&signable_bytes, secret.as_secret_bytes()) + .expect("signing should succeed"); witnesses.push(AddressWitness::P2pkh { signature: BinaryData::new(signature.to_vec()), @@ -9641,21 +9640,21 @@ mod tests { let mut rng = StdRng::seed_from_u64(6003); let public_keys = create_default_public_keys(&mut rng, platform_version); - let secp = Secp256k1::new(); // Create 3 keys for 2-of-3 multisig let secrets: Vec<_> = (1..=3) .map(|i| { let mut key_bytes = [0u8; 32]; key_bytes[0] = i + 10; - dpp::dashcore::secp256k1::SecretKey::from_slice(&key_bytes).expect("valid") + dpp::dashcore::secp256k1::SecretKey::from_secret_bytes(key_bytes) + .expect("valid") }) .collect(); let pubkeys: Vec<[u8; 33]> = secrets .iter() .map(|secret| { - let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(&secp, secret); + let raw_pubkey = RawSecp256k1PublicKey::from_secret_key(secret); raw_pubkey.serialize() }) .collect(); @@ -9700,9 +9699,9 @@ mod tests { .expect("should get signable bytes"); // Sign with first 2 keys (2-of-3) using DER signatures for P2SH - let sig1 = dpp::dashcore::signer::sign(&signable_bytes, secrets[0].as_ref()) + let sig1 = dpp::dashcore::signer::sign(&signable_bytes, secrets[0].as_secret_bytes()) .expect("signing should succeed"); - let sig2 = dpp::dashcore::signer::sign(&signable_bytes, secrets[1].as_ref()) + let sig2 = dpp::dashcore::signer::sign(&signable_bytes, secrets[1].as_secret_bytes()) .expect("signing should succeed"); let _signed_transition = IdentityCreateFromAddressesTransition::V0( diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs index 96fddc39cd7..25d5ca0b898 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs @@ -139,7 +139,9 @@ mod tests { use dpp::consensus::codes::ErrorWithCode; use dpp::consensus::ConsensusError; use dpp::dash_to_credits; - use dpp::dashcore::key::{Keypair, Secp256k1}; + use dpp::dashcore::key::Keypair; + use dpp::dashcore::secp256k1::rand::rngs::StdRng as SecpStdRng; + use dpp::dashcore::secp256k1::rand::SeedableRng as _; use dpp::dashcore::signer; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::identifier::Identifier; @@ -257,11 +259,9 @@ mod tests { let platform_state = platform.state.load(); - let secp = Secp256k1::new(); + let mut rng = SecpStdRng::seed_from_u64(292); - let mut rng = StdRng::seed_from_u64(292); - - let new_key_pair = Keypair::new(&secp, &mut rng); + let new_key_pair = Keypair::new(&mut rng); let mut new_key = IdentityPublicKeyInCreationV0 { id: 2, @@ -294,7 +294,7 @@ mod tests { let secret = new_key_pair.secret_key(); let signature = - signer::sign(&signable_bytes, &secret.secret_bytes()).expect("expected to sign"); + signer::sign(&signable_bytes, &secret.to_secret_bytes()).expect("expected to sign"); new_key.signature = signature.to_vec().into(); @@ -540,9 +540,8 @@ mod tests { document_type_name: "profile".into(), }; let platform_state = platform.state.load(); - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(292); - let new_key_pair = Keypair::new(&secp, &mut rng); + let mut rng = SecpStdRng::seed_from_u64(292); + let new_key_pair = Keypair::new(&mut rng); let mut new_key = IdentityPublicKeyInCreationV0 { id: 2, purpose: Purpose::AUTHENTICATION, @@ -567,11 +566,13 @@ mod tests { .into() }; let signable_bytes = build(new_key.clone()).signable_bytes().unwrap(); - new_key.signature = - signer::sign(&signable_bytes, &new_key_pair.secret_key().secret_bytes()) - .unwrap() - .to_vec() - .into(); + new_key.signature = signer::sign( + &signable_bytes, + &new_key_pair.secret_key().to_secret_bytes(), + ) + .unwrap() + .to_vec() + .into(); let mut update_transition = build(new_key); update_transition .set_signature(signer.sign(&key, signable_bytes.as_slice()).await.unwrap()); @@ -749,9 +750,8 @@ mod tests { id: contract_group_id, }; let platform_state = platform.state.load(); - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(293); - let new_key_pair = Keypair::new(&secp, &mut rng); + let mut rng = SecpStdRng::seed_from_u64(293); + let new_key_pair = Keypair::new(&mut rng); let build = |revision: u64, nonce: u64, add: Vec, disable| { StateTransition::from(IdentityUpdateTransition::from(IdentityUpdateTransitionV0 { @@ -783,11 +783,13 @@ mod tests { let signable_bytes = build(revision, nonce, vec![new_key.clone()], vec![]) .signable_bytes() .unwrap(); - new_key.signature = - signer::sign(&signable_bytes, &new_key_pair.secret_key().secret_bytes()) - .unwrap() - .to_vec() - .into(); + new_key.signature = signer::sign( + &signable_bytes, + &new_key_pair.secret_key().to_secret_bytes(), + ) + .unwrap() + .to_vec() + .into(); let mut update = build(revision, nonce, vec![new_key], vec![]); update.set_signature(signer.sign(&key, signable_bytes.as_slice()).await.unwrap()); signed_updates.push(update); @@ -1107,11 +1109,10 @@ mod tests { .load_dashpay(version) .unwrap(); let bounds = ContractBounds::SingleContract { id: dashpay.id() }; - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(292); + let mut rng = SecpStdRng::seed_from_u64(292); let pairs: BTreeMap = [2u32, 3, 4] .into_iter() - .map(|id| (id, Keypair::new(&secp, &mut rng))) + .map(|id| (id, Keypair::new(&mut rng))) .collect(); let bound_key = |id: u32| IdentityPublicKeyInCreationV0 { id, @@ -1226,7 +1227,7 @@ mod tests { let mut adds = vec![bound_key(3), bound_key(2)]; let signable = unsigned(1, adds.clone(), vec![]).signable_bytes().unwrap(); for key in &mut adds { - key.signature = signer::sign(&signable, &pairs[&key.id].secret_key().secret_bytes()) + key.signature = signer::sign(&signable, &pairs[&key.id].secret_key().to_secret_bytes()) .unwrap() .to_vec() .into(); @@ -1241,7 +1242,7 @@ mod tests { let mut adds = vec![bound_key(4)]; let signable = unsigned(2, adds.clone(), vec![3]).signable_bytes().unwrap(); for key in &mut adds { - key.signature = signer::sign(&signable, &pairs[&key.id].secret_key().secret_bytes()) + key.signature = signer::sign(&signable, &pairs[&key.id].secret_key().to_secret_bytes()) .unwrap() .to_vec() .into(); @@ -1673,11 +1674,9 @@ mod tests { let platform_state = platform.state.load(); - let secp = Secp256k1::new(); + let mut rng = SecpStdRng::seed_from_u64(1292); - let mut rng = StdRng::seed_from_u64(1292); - - let new_key_pair = Keypair::new(&secp, &mut rng); + let new_key_pair = Keypair::new(&mut rng); let new_key = IdentityPublicKeyInCreationV0 { id: 2, @@ -1862,11 +1861,9 @@ mod tests { ) .await; - let secp = Secp256k1::new(); - - let mut rng = StdRng::seed_from_u64(1292); + let mut rng = SecpStdRng::seed_from_u64(1292); - let new_key_pair = Keypair::new(&secp, &mut rng); + let new_key_pair = Keypair::new(&mut rng); let mut new_key = IdentityPublicKeyInCreationV0 { id: 2, @@ -1902,7 +1899,7 @@ mod tests { // Sign the new key with its own private key let secret = new_key_pair.secret_key(); let signature = - signer::sign(&signable_bytes, &secret.secret_bytes()).expect("expected to sign"); + signer::sign(&signable_bytes, &secret.to_secret_bytes()).expect("expected to sign"); new_key.signature = signature.to_vec().into(); @@ -2147,14 +2144,13 @@ mod tests { let bounds = ContractBounds::SingleContract { id: data_contract.id(), }; - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(1292); + let mut rng = SecpStdRng::seed_from_u64(1292); // Two encryption keys, the newer one listed first: both write the encryption // current-key alias, and the one naming the highest key id must win. let pairs: Vec<(u32, Purpose, Keypair)> = vec![ - (4, Purpose::ENCRYPTION, Keypair::new(&secp, &mut rng)), - (2, Purpose::ENCRYPTION, Keypair::new(&secp, &mut rng)), - (3, Purpose::DECRYPTION, Keypair::new(&secp, &mut rng)), + (4, Purpose::ENCRYPTION, Keypair::new(&mut rng)), + (2, Purpose::ENCRYPTION, Keypair::new(&mut rng)), + (3, Purpose::DECRYPTION, Keypair::new(&mut rng)), ]; let mut adds: Vec = pairs .iter() @@ -2270,7 +2266,7 @@ mod tests { .signable_bytes() .expect("expected signable bytes"); for (key, (_, _, pair)) in adds.iter_mut().zip(&pairs) { - key.signature = signer::sign(&signable, &pair.secret_key().secret_bytes()) + key.signature = signer::sign(&signable, &pair.secret_key().to_secret_bytes()) .expect("expected to sign") .to_vec() .into(); @@ -2637,11 +2633,9 @@ mod tests { ) .await; - let secp = Secp256k1::new(); + let mut rng = SecpStdRng::seed_from_u64(1292); - let mut rng = StdRng::seed_from_u64(1292); - - let new_key_pair = Keypair::new(&secp, &mut rng); + let new_key_pair = Keypair::new(&mut rng); let mut new_key = IdentityPublicKeyInCreationV0 { id: 2, @@ -2678,7 +2672,7 @@ mod tests { // Sign the new key with its own private key let secret = new_key_pair.secret_key(); let signature = - signer::sign(&signable_bytes, &secret.secret_bytes()).expect("expected to sign"); + signer::sign(&signable_bytes, &secret.to_secret_bytes()).expect("expected to sign"); new_key.signature = signature.to_vec().into(); @@ -3011,9 +3005,8 @@ mod tests { let platform_state = platform.state.load(); - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(292); - let new_key_pair = Keypair::new(&secp, &mut rng); + let mut rng = SecpStdRng::seed_from_u64(292); + let new_key_pair = Keypair::new(&mut rng); // Add a key with id 2 and also disable key id 2 in the same transition let new_key = IdentityPublicKeyInCreationV0 { @@ -3241,9 +3234,8 @@ mod tests { let platform_state = platform.state.load(); - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(292); - let new_key_pair = Keypair::new(&secp, &mut rng); + let mut rng = SecpStdRng::seed_from_u64(292); + let new_key_pair = Keypair::new(&mut rng); let signable_transition: IdentityUpdateTransition = IdentityUpdateTransitionV0 { identity_id: identity.id(), @@ -3276,7 +3268,7 @@ mod tests { // Sign the new key let secret = new_key_pair.secret_key(); let key_sig = - signer::sign(&signable_bytes, &secret.secret_bytes()).expect("expected to sign"); + signer::sign(&signable_bytes, &secret.to_secret_bytes()).expect("expected to sign"); let mut new_key = IdentityPublicKeyInCreationV0 { id: 1, // existing key ID @@ -3377,9 +3369,8 @@ mod tests { setup_identity_return_master_key(&mut platform, 958, dash_to_credits!(0.1)); let platform_state = platform.state.load(); - let secp = Secp256k1::new(); - let mut rng = StdRng::seed_from_u64(292); - let new_key_pair = Keypair::new(&secp, &mut rng); + let mut rng = SecpStdRng::seed_from_u64(292); + let new_key_pair = Keypair::new(&mut rng); let mut new_key = IdentityPublicKeyInCreationV1 { id: NEW_KEY_ID, purpose: added_key.purpose, @@ -3413,11 +3404,13 @@ mod tests { let signable_bytes = transition_adding(new_key.clone()) .signable_bytes() .expect("expected signable bytes"); - new_key.signature = - signer::sign(&signable_bytes, &new_key_pair.secret_key().secret_bytes()) - .expect("expected to sign") - .to_vec() - .into(); + new_key.signature = signer::sign( + &signable_bytes, + &new_key_pair.secret_key().to_secret_bytes(), + ) + .expect("expected to sign") + .to_vec() + .into(); let mut update_transition = transition_adding(new_key); update_transition.set_signature( diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield_from_asset_lock/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield_from_asset_lock/tests.rs index a61c6b746fe..998e9851790 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield_from_asset_lock/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shield_from_asset_lock/tests.rs @@ -625,7 +625,7 @@ mod tests { let transition = create_signed_shield_from_asset_lock_transition( asset_lock_proof, - &wrong_private_key.inner.secret_bytes(), // Wrong key + &wrong_private_key.inner.to_secret_bytes(), // Wrong key vec![create_dummy_serialized_action()], 5000, [42u8; 32], diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/test_helpers.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/test_helpers.rs index 983772d50ab..830f8e55124 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/test_helpers.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/test_helpers.rs @@ -17,7 +17,7 @@ use dpp::block::block_info::BlockInfo; use dpp::consensus::ConsensusError; use dpp::dashcore::blockdata::script::ScriptBuf; use dpp::dashcore::hashes::{sha256, Hash}; -use dpp::dashcore::secp256k1::{PublicKey as RawPublicKey, Secp256k1, SecretKey as RawSecretKey}; +use dpp::dashcore::secp256k1::{PublicKey as RawPublicKey, SecretKey as RawSecretKey}; use dpp::dashcore::PublicKey; use dpp::identity::signer::Signer; use dpp::platform_value::BinaryData; @@ -51,8 +51,6 @@ pub use dpp::dashcore::blockdata::opcodes::all::{ pub use dpp::dashcore::blockdata::script::ScriptBuf as TestScriptBuf; pub use dpp::dashcore::hashes::Hash as TestHash; #[allow(unused_imports)] -pub use dpp::dashcore::secp256k1::Secp256k1 as TestSecp256k1; -#[allow(unused_imports)] pub use dpp::dashcore::PublicKey as TestPublicKey; pub use dpp::ProtocolError as TestProtocolError; @@ -87,19 +85,18 @@ impl TestAddressSigner { } pub fn create_keypair(seed: [u8; 32]) -> (RawSecretKey, PublicKey) { - let secp = Secp256k1::new(); // Hash the seed to ensure it's always a valid secret key // (non-zero, less than curve order). Raw seeds like [0u8; 32] are invalid. let hashed_seed = sha256::Hash::hash(&seed); - let secret_key = - RawSecretKey::from_byte_array(hashed_seed.as_byte_array()).expect("valid secret key"); - let raw_public_key = RawPublicKey::from_secret_key(&secp, &secret_key); + let secret_key = RawSecretKey::from_secret_bytes(*hashed_seed.as_byte_array()) + .expect("valid secret key"); + let raw_public_key = RawPublicKey::from_secret_key(&secret_key); let public_key = PublicKey::new(raw_public_key); (secret_key, public_key) } pub fn sign_data(data: &[u8], secret_key: &RawSecretKey) -> Vec { - dpp::dashcore::signer::sign(data, secret_key.as_ref()) + dpp::dashcore::signer::sign(data, secret_key.as_secret_bytes()) .expect("signing should succeed") .to_vec() } @@ -153,7 +150,7 @@ impl TestAddressSigner { PlatformAddress::P2pkh(hash) => self .p2pkh_keys .get(hash) - .map(|entry| entry.secret_key.secret_bytes()), + .map(|entry| entry.secret_key.to_secret_bytes()), _ => None, } } diff --git a/packages/rs-drive-abci/src/mimic/mod.rs b/packages/rs-drive-abci/src/mimic/mod.rs index 9434a5572b9..a05b0f146c4 100644 --- a/packages/rs-drive-abci/src/mimic/mod.rs +++ b/packages/rs-drive-abci/src/mimic/mod.rs @@ -13,7 +13,7 @@ use crate::platform_types::withdrawal::unsigned_withdrawal_txs::v0::{ use crate::rpc::core::CoreRPCLike; use ciborium::Value as CborValue; use dpp::block::block_info::BlockInfo; -use dpp::bls_signatures::SignatureSchemes; + use dpp::consensus::ConsensusError; use dpp::dashcore::hashes::Hash; use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; @@ -538,10 +538,10 @@ impl FullAbciApplication<'_, C> { ); let block_signature = current_quorum .private_key - .sign(SignatureSchemes::Basic, digest.as_slice()) + .sign(digest.as_slice()) .expect("expected to be able to sign"); - commit_info.block_signature = block_signature.as_raw_value().to_compressed().to_vec(); + commit_info.block_signature = block_signature.to_bytes().to_vec(); } else { commit_info.block_signature = [0u8; 96].to_vec(); } diff --git a/packages/rs-drive-abci/src/mimic/test_quorum.rs b/packages/rs-drive-abci/src/mimic/test_quorum.rs index 9521f88c46e..1adebbf840f 100644 --- a/packages/rs-drive-abci/src/mimic/test_quorum.rs +++ b/packages/rs-drive-abci/src/mimic/test_quorum.rs @@ -1,8 +1,6 @@ use crate::platform_types::validator::v0::ValidatorV0; use crate::platform_types::validator_set::v0::ValidatorSetV0; -use dpp::bls_signatures::{ - Bls12381G2Impl, PublicKey as BlsPublicKey, PublicKey, SecretKey as BlsPrivateKey, SecretKey, -}; +use dpp::bls::{PublicKey as BlsPublicKey, PublicKey, SecretKey as BlsPrivateKey, SecretKey}; use dpp::dashcore::hashes::Hash; use dpp::dashcore::{ProTxHash, PubkeyHash, QuorumHash}; use dpp::dashcore_rpc::dashcore_rpc_json::{QuorumInfoResult, QuorumMember, QuorumType}; @@ -18,9 +16,9 @@ pub struct ValidatorInQuorum { /// The hash of the transaction that identifies this validator in the network. pub pro_tx_hash: ProTxHash, /// The private key for this validator's BLS signature scheme. - pub private_key: BlsPrivateKey, + pub private_key: BlsPrivateKey, /// The public key for this validator's BLS signature scheme. - pub public_key: BlsPublicKey, + pub public_key: BlsPublicKey, /// The node address pub node_ip: String, /// The node id @@ -103,9 +101,9 @@ pub struct TestQuorumInfo { /// A map of validators indexed by their `ProTxHash` identifiers. pub validator_map: BTreeMap, /// The private key used to sign messages for the quorum (for testing purposes only). - pub private_key: BlsPrivateKey, + pub private_key: BlsPrivateKey, /// The public key corresponding to the private key used for signing. - pub public_key: BlsPublicKey, + pub public_key: BlsPublicKey, } fn random_ipv4_address(rng: &mut StdRng) -> Ipv4Addr { @@ -274,7 +272,7 @@ impl From<&TestQuorumInfo> for QuorumInfoResult { pro_tx_hash: *pro_tx_hash, pub_key_operator: vec![], //doesn't matter valid: true, - pub_key_share: Some(public_key.0.to_compressed().to_vec()), + pub_key_share: Some(public_key.to_bytes().to_vec()), } }) .collect(); @@ -285,7 +283,7 @@ impl From<&TestQuorumInfo> for QuorumInfoResult { quorum_index: 0, mined_block: vec![], members, - quorum_public_key: public_key.0.to_compressed().to_vec(), + quorum_public_key: public_key.to_bytes().to_vec(), secret_key_share: None, } } diff --git a/packages/rs-drive-abci/src/platform_types/commit/mod.rs b/packages/rs-drive-abci/src/platform_types/commit/mod.rs index 153de511e26..568c5028f5a 100644 --- a/packages/rs-drive-abci/src/platform_types/commit/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/commit/mod.rs @@ -3,8 +3,8 @@ use crate::error::execution::ExecutionError; use crate::error::Error; use crate::platform_types::cleaned_abci_messages::{cleaned_block_id, cleaned_commit_info}; use crate::platform_types::commit::v0::CommitV0; -use dpp::bls_signatures; -use dpp::bls_signatures::Bls12381G2Impl; +use dpp::bls; + use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType; use dpp::validation::SimpleValidationResult; use dpp::version::PlatformVersion; @@ -83,7 +83,7 @@ impl Commit { pub fn verify_signature( &self, signature: &[u8; 96], - public_key: &bls_signatures::PublicKey, + public_key: &bls::PublicKey, ) -> SimpleValidationResult { match self { Commit::V0(v0) => v0.verify_signature(signature, public_key), diff --git a/packages/rs-drive-abci/src/platform_types/commit/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/commit/v0/mod.rs index af9fdc918ce..7d131cc6be3 100644 --- a/packages/rs-drive-abci/src/platform_types/commit/v0/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/commit/v0/mod.rs @@ -4,8 +4,8 @@ pub mod accessors; use crate::abci::AbciError; use crate::platform_types::cleaned_abci_messages::{cleaned_block_id, cleaned_commit_info}; -use dpp::bls_signatures; -use dpp::bls_signatures::{Bls12381G2Impl, BlsError, Pairing, Signature}; +use dpp::bls; +use dpp::bls::{BlsError, Signature}; use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType; use dpp::validation::{SimpleValidationResult, ValidationResult}; use tenderdash_abci::proto; @@ -82,7 +82,7 @@ impl CommitV0 { pub(super) fn verify_signature( &self, signature: &[u8; 96], - public_key: &bls_signatures::PublicKey, + public_key: &bls::PublicKey, ) -> SimpleValidationResult { if signature == &[0; 96] { return ValidationResult::new_with_error(AbciError::BadRequest( @@ -91,18 +91,16 @@ impl CommitV0 { } // We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result - let g2_element = match ::Signature::from_compressed(signature) - .into_option() - .ok_or(AbciError::BlsErrorOfTenderdashThresholdMechanism( + let signature = match Signature::from_compressed(signature).ok_or( + AbciError::BlsErrorOfTenderdashThresholdMechanism( BlsError::InvalidSignature, "verification of a commit signature".to_string(), - )) { + ), + ) { Ok(signature) => signature, Err(e) => return ValidationResult::new_with_error(e), }; - let signature = Signature::Basic(g2_element); - //todo: maybe cache this to lower the chance of a hashing based attack (forcing the // same calculation each time) let quorum_hash = &self.inner.quorum_hash[..] @@ -139,7 +137,7 @@ mod test { use super::CommitV0; use crate::platform_types::cleaned_abci_messages::cleaned_commit_info::v0::CleanedCommitInfo; - use dpp::bls_signatures::PublicKey; + use dpp::bls::PublicKey; use dpp::dashcore_rpc::{ dashcore::hashes::sha256, dashcore::hashes::Hash, dashcore_rpc_json::QuorumType, }; diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/old_structures/mod.rs b/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/old_structures/mod.rs index a5b7a11c78d..8ad16315f56 100644 --- a/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/old_structures/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/platform_state/platform_state_for_saving/v0/old_structures/mod.rs @@ -1,5 +1,5 @@ #[cfg(feature = "bls-signatures")] -use dpp::bls_signatures::PublicKey; +use dpp::bls::PublicKey; use dpp::dashcore::{ProTxHash, PubkeyHash, QuorumHash}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/README.md b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/README.md new file mode 100644 index 00000000000..2103cd414e6 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/README.md @@ -0,0 +1,103 @@ +# Historical quorum storage constants + +The frozen bytes live in `../storage_vectors.rs` as hex constants. The filenames +below identify generator outputs; no separate hex files are needed by the tests. +Checksums describe those original outputs, including their final newline. + +## Historical V0 quorum storage + +`quorum-storage-v0.hex` contains a synthetic, complete +`SignatureVerificationQuorumSetForSaving::V0` encoded by Platform commit +`7c73e983b4c3cd9b5e2ead34bbe0360646b7b4d9`, before the DPP BLS backend migration. +It uses `grovedb-bincode` with `standard().with_big_endian().with_no_limit()` +and the C++ `bls-signatures` public-key Serde implementation at revision +`0842b17583888e8f46c252a4ee84cdfd58e0546f`. + +The hex file (including its final newline) has SHA-256 +`8a2c8e59633ca1b126e3c0cc358a9347667d0f54068a874b1ae1435b85af830f` +and encodes 350 bytes. + +The four public keys come from DPP's frozen BLS compatibility vectors. Current +quorums have repeated-byte hashes `11` and `22`, with indexes `None` and `Some(0)`; +previous quorums have hashes `33` and `44`, with indexes `Some(1)` and `None`. +The previous-quorum heights are 1000 (last active), 1008 (updated), and +`Some(900)` (previous change). Configuration is `Llmq400_60`, four active signers, +no rotation, and a 288-block window. + +The test decodes the production storage enum and converts it to the runtime +quorum set, exercising the historical C++-to-DPP key conversion for both lists. +Expected keys and metadata are asserted independently of the current writer. + +To reproduce, copy `generate-quorum-storage-v0.rs` from this directory to +`packages/rs-drive-abci/examples/generate_quorum_storage_v0.rs` in a separate +checkout of the commit above, then run from that checkout: + +```sh +cargo run -p drive-abci --example generate_quorum_storage_v0 --all-features --locked -- /data/tmp/quorum-storage-v0.hex +``` + +Compare the generated file with the committed fixture. Do not regenerate the +expected bytes with the migrated backend. The generator explicitly selects V0; +the ordinary runtime-to-storage conversion writes V2. + +## Historical V1/V2 quorum, saved-state and checkpoint records + +`generate-quorum-storage.rs` was run against the same pre-migration Platform +commit above, using `blsful` at `0c34a7a488a0bd1c9a9a2196e793b303ad35c900`. +The configuration, keys, hashes, indexes and previous-quorum heights are the same +as the V0 fixture. V1 stores previous keys with the C++ library; V2 stores both +lists with blsful. Its public keys occupy exactly 48 compressed bytes, without +a length prefix. + +The full state fixtures contain both chain-lock and instant-lock quorum sets, +protocol versions 13 (current) and 14 (next), and empty masternode/validator +collections. `platform-state-v1.hex` comes from `serialize_to_bytes`; +`checkpoint-platform-state.hex` comes from `serialize_standalone_to_bytes`, the +writer used for checkpoint `platform_state.bin`. These two files intentionally +have identical bytes. `platform-state-v2.hex` contains the per-entry storage +record; its external collections are empty. These are synthetic historical +records, not snapshots of a live database. + +Copy the generator to `packages/rs-drive-abci/examples/generate_quorum_storage.rs` +in that historical checkout and run: + +```sh +cargo run -p drive-abci --example generate_quorum_storage --all-features --locked -- /path/to/output-directory +``` + +Checksums below include the final newline in each hex file. + +| File | Decoded bytes | SHA-256 | +| --- | ---: | --- | +| `quorum-storage-v1.hex` | 348 | `33cfbd097457cce4d8d5fadc214a12beee7a03b3b2a6deecabe77e74299bfee1` | +| `quorum-storage-v2.hex` | 346 | `bc2eefbb94cd7e8f2dae643eed1d76bff3953412955bb8a96960f755b34235de` | +| `platform-state-v1.hex` | 734 | `e2b83244feff85195d45fe4092ea85d298037f53cecdddd55c0eb7a5dda27ad7` | +| `checkpoint-platform-state.hex` | 734 | `e2b83244feff85195d45fe4092ea85d298037f53cecdddd55c0eb7a5dda27ad7` | +| `platform-state-v2.hex` | 732 | `d5eb9be1fac002863a22f1cfec3bb770ee9f67dde44ed3cba4734b18135f9616` | + +## Populated GroveDB records + +`generate-populated-storage.rs` runs on Platform +`6499c680c6bc311e8933f396423a79459e2a88bd`, before #5307, with rust-dashcore +`40268cc0402a8933ec539f16b2d634c4e25876ad` and the same historical blsful revision. +Copy it to that checkout's `packages/rs-drive-abci/examples/generate_populated_storage.rs`: + +```sh +cargo run -p drive-abci --example generate_populated_storage --all-features --locked -- /path/to/output +``` + +The corresponding `storage_vectors.rs` constants add: + +- `MASTERNODE_REGULAR` (241 bytes) and `MASTERNODE_EVO` (279 bytes): actual + per-entry writers, including IPv4/IPv6, operator keys and a non-palindromic node ID. +- `VALIDATOR_SET_ENTRY` (243 bytes): a versioned set with one member and distinct + member/threshold BLS keys. +- `POPULATED_PLATFORM_STATE_V1` (1904 bytes) and `POPULATED_PLATFORM_STATE_V2` + (764 bytes): two masternodes, one validator set and both current/previous quorum lists. +- `POPULATED_CHECKPOINT_PLATFORM_STATE`: the standalone checkpoint writer's output, + verified identical to V1 and represented by a constant alias. + +`should_preserve_historical_grovedb_masternode_and_validator_entries` exercises +the production entry codecs. `should_restore_historical_populated_state_and_checkpoint` +rebuilds V2 from its external entries and checks that its standalone checkpoint +still matches the historical bytes. These are synthetic records, not live-chain snapshots. diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-populated-storage.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-populated-storage.rs new file mode 100644 index 00000000000..9d0b54b5bb6 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-populated-storage.rs @@ -0,0 +1,195 @@ +//! Run as a drive-abci example on Platform 6499c680c6bc311e8933f396423a79459e2a88bd. +//! `cargo run -p drive-abci --example generate_populated_storage --all-features --locked -- /path/to/output` + +use dpp::core_types::validator::v0::ValidatorV0; +use dpp::core_types::validator_set::{v0::ValidatorSetV0, ValidatorSet}; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::QuorumHash; +use dpp::dashcore::{ProTxHash, PubkeyHash, Txid}; +use dpp::dashcore_rpc::json::QuorumType; +use dpp::dashcore_rpc::json::{MasternodeListItem, MasternodeType}; +use dpp::serialization::PlatformSerializable; +use dpp::version::PlatformVersion; +use drive_abci::config::{ChainLockConfig, PlatformConfig}; +use drive_abci::platform_types::masternode::{ + v0::{MasternodeStateV0, MasternodeV0}, + Masternode, +}; +use drive_abci::platform_types::platform_state::platform_state_for_saving::v2::{ + serialize_masternode_entry, serialize_validator_set_entry, +}; +use drive_abci::platform_types::platform_state::{ + platform_state_for_saving::{v2::PlatformStateForSavingV2, PlatformStateForSaving}, + PlatformState, +}; +use drive_abci::platform_types::signature_verification_quorum_set::{ + Quorums, SignatureVerificationQuorumSetForSaving, SignatureVerificationQuorumSetV0, + SignatureVerificationQuorumSetV0Methods, VerificationQuorum, +}; +use std::collections::BTreeMap; +use std::path::Path; + +const PUBLIC_KEYS: [&str; 4] = [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", +]; + +fn quorums(entries: &[(u8, usize, Option)]) -> Quorums { + entries + .iter() + .map(|&(hash, key, index)| { + ( + QuorumHash::from_byte_array([hash; 32]), + VerificationQuorum { + public_key: hex::decode(PUBLIC_KEYS[key]) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + index, + }, + ) + }) + .collect() +} + +fn main() { + let mut runtime = SignatureVerificationQuorumSetV0::new(&ChainLockConfig { + quorum_type: QuorumType::Llmq400_60, + quorum_size: 400, + quorum_window: 288, + quorum_active_signers: 4, + quorum_rotation: false, + }); + runtime.set_current_quorums(quorums(&[(0x11, 0, None), (0x22, 1, Some(0))])); + runtime.set_previous_past_quorums(Quorums::default(), 899, 900); + runtime.set_previous_past_quorums(quorums(&[(0x33, 2, Some(1)), (0x44, 3, None)]), 1000, 1008); + let output = std::env::args().nth(1).expect("fixture output directory"); + let output = Path::new(&output); + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + for (name, stored) in [ + ( + "quorum-storage-v1.hex", + SignatureVerificationQuorumSetForSaving::V1(runtime.clone().into()), + ), + ( + "quorum-storage-v2.hex", + SignatureVerificationQuorumSetForSaving::V2(runtime.clone().into()), + ), + ] { + write( + output, + name, + bincode::encode_to_vec(stored, config).unwrap(), + ); + } + let mut state = + PlatformState::default_with_protocol_versions(13, 14, &PlatformConfig::default()).unwrap(); + state.chain_lock_validating_quorums = runtime.clone().into(); + state.instant_lock_validating_quorums = runtime.into(); + for (tag, node_type, service) in [ + (0x31, MasternodeType::Regular, "1.2.3.4:19999"), + (0x41, MasternodeType::Evo, "[2001:db8::1]:19999"), + ] { + let evo = node_type == MasternodeType::Evo; + let node: MasternodeListItem = Masternode::V0(MasternodeV0 { + node_type, + pro_tx_hash: ProTxHash::from_byte_array([tag; 32]), + collateral_hash: Txid::from_byte_array([tag + 1; 32]), + collateral_index: 1000, + collateral_address: [0x21; 20], + operator_reward: 1.5, + state: MasternodeStateV0 { + service: service.parse().unwrap(), + registered_height: 100, + pose_revived_height: Some(200), + pose_ban_height: None, + revocation_reason: 0, + owner_address: [0x22; 20], + voting_address: [0x23; 20], + payout_address: [0x24; 20], + pub_key_operator: hex::decode(PUBLIC_KEYS[0]).unwrap(), + operator_payout_address: Some([0x25; 20]), + platform_node_id: evo.then_some(std::array::from_fn(|i| i as u8 + 1)), + platform_p2p_port: evo.then_some(26656), + platform_http_port: evo.then_some(443), + }, + }) + .into(); + write( + output, + if evo { + "masternode-evo.hex" + } else { + "masternode-regular.hex" + }, + serialize_masternode_entry(&node, PlatformVersion::latest()).unwrap(), + ); + if evo { + state + .hpmn_masternode_list + .insert(node.pro_tx_hash, node.clone()); + } + state.full_masternode_list.insert(node.pro_tx_hash, node); + } + let validator = ValidatorV0 { + pro_tx_hash: ProTxHash::from_byte_array([0x41; 32]), + public_key: Some( + hex::decode(PUBLIC_KEYS[1]) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + ), + node_ip: "2001:db8::1".into(), + node_id: PubkeyHash::from_byte_array(std::array::from_fn(|i| i as u8 + 1)), + core_port: 19999, + platform_http_port: 443, + platform_p2p_port: 26656, + is_banned: false, + }; + let quorum_hash = QuorumHash::from_byte_array([0x55; 32]); + let set = ValidatorSet::V0(ValidatorSetV0 { + quorum_hash, + quorum_index: Some(2), + core_height: 1000, + members: BTreeMap::from([(validator.pro_tx_hash, validator)]), + threshold_public_key: hex::decode(PUBLIC_KEYS[2]) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + }); + write( + output, + "validator-set-entry.hex", + serialize_validator_set_entry(&set).unwrap(), + ); + state.validator_sets.insert(quorum_hash, set); + state.current_validator_set_quorum_hash = quorum_hash; + write( + output, + "populated-platform-state-v1.hex", + state.serialize_to_bytes().unwrap(), + ); + write( + output, + "populated-checkpoint-platform-state.hex", + state.serialize_standalone_to_bytes().unwrap(), + ); + let stored = PlatformStateForSaving::V2(PlatformStateForSavingV2::from(&state)); + write( + output, + "populated-platform-state-v2.hex", + bincode::encode_to_vec(stored, config).unwrap(), + ); +} + +fn write(output: &Path, name: &str, bytes: Vec) { + println!("{name}: {} bytes", bytes.len()); + std::fs::write(output.join(name), format!("{}\n", hex::encode(bytes))).unwrap(); +} diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-quorum-storage-v0.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-quorum-storage-v0.rs new file mode 100644 index 00000000000..12d3f4574a4 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-quorum-storage-v0.rs @@ -0,0 +1,60 @@ +//! Run as a drive-abci example at the pre-migration revision documented in README.md. + +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::QuorumHash; +use dpp::dashcore_rpc::json::QuorumType; +use drive_abci::config::ChainLockConfig; +use drive_abci::platform_types::signature_verification_quorum_set::{ + Quorums, SignatureVerificationQuorumSetForSaving, SignatureVerificationQuorumSetV0, + SignatureVerificationQuorumSetV0Methods, VerificationQuorum, +}; + +const PUBLIC_KEYS: [&str; 4] = [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", +]; + +fn quorums(entries: &[(u8, usize, Option)]) -> Quorums { + entries + .iter() + .map(|&(hash, key, index)| { + ( + QuorumHash::from_byte_array([hash; 32]), + VerificationQuorum { + public_key: hex::decode(PUBLIC_KEYS[key]) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + index, + }, + ) + }) + .collect() +} + +fn main() { + let mut runtime = SignatureVerificationQuorumSetV0::new(&ChainLockConfig { + quorum_type: QuorumType::Llmq400_60, + quorum_size: 400, + quorum_window: 288, + quorum_active_signers: 4, + quorum_rotation: false, + }); + runtime.set_current_quorums(quorums(&[(0x11, 0, None), (0x22, 1, Some(0))])); + runtime.set_previous_past_quorums(Quorums::default(), 899, 900); + runtime.set_previous_past_quorums(quorums(&[(0x33, 2, Some(1)), (0x44, 3, None)]), 1000, 1008); + // The normal runtime conversion writes V2; explicitly select the historical V0 writer. + let stored = SignatureVerificationQuorumSetForSaving::V0(runtime.into()); + let bytes = bincode::encode_to_vec( + stored, + bincode::config::standard() + .with_big_endian() + .with_no_limit(), + ) + .unwrap(); + let destination = std::env::args().nth(1).expect("fixture output path"); + std::fs::write(destination, format!("{}\n", hex::encode(bytes))).unwrap(); +} diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-quorum-storage.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-quorum-storage.rs new file mode 100644 index 00000000000..9cc5bc268d3 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/fixtures/generate-quorum-storage.rs @@ -0,0 +1,101 @@ +//! Run as a drive-abci example at the pre-migration revision documented in README.md. + +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::QuorumHash; +use dpp::dashcore_rpc::json::QuorumType; +use dpp::serialization::PlatformSerializable; +use drive_abci::config::{ChainLockConfig, PlatformConfig}; +use drive_abci::platform_types::platform_state::{ + platform_state_for_saving::{v2::PlatformStateForSavingV2, PlatformStateForSaving}, + PlatformState, +}; +use drive_abci::platform_types::signature_verification_quorum_set::{ + Quorums, SignatureVerificationQuorumSetForSaving, SignatureVerificationQuorumSetV0, + SignatureVerificationQuorumSetV0Methods, VerificationQuorum, +}; +use std::path::Path; + +const PUBLIC_KEYS: [&str; 4] = [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", +]; + +fn quorums(entries: &[(u8, usize, Option)]) -> Quorums { + entries + .iter() + .map(|&(hash, key, index)| { + ( + QuorumHash::from_byte_array([hash; 32]), + VerificationQuorum { + public_key: hex::decode(PUBLIC_KEYS[key]) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + index, + }, + ) + }) + .collect() +} + +fn main() { + let mut runtime = SignatureVerificationQuorumSetV0::new(&ChainLockConfig { + quorum_type: QuorumType::Llmq400_60, + quorum_size: 400, + quorum_window: 288, + quorum_active_signers: 4, + quorum_rotation: false, + }); + runtime.set_current_quorums(quorums(&[(0x11, 0, None), (0x22, 1, Some(0))])); + runtime.set_previous_past_quorums(Quorums::default(), 899, 900); + runtime.set_previous_past_quorums(quorums(&[(0x33, 2, Some(1)), (0x44, 3, None)]), 1000, 1008); + let output = std::env::args().nth(1).expect("fixture output directory"); + let output = Path::new(&output); + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + for (name, stored) in [ + ( + "quorum-storage-v1.hex", + SignatureVerificationQuorumSetForSaving::V1(runtime.clone().into()), + ), + ( + "quorum-storage-v2.hex", + SignatureVerificationQuorumSetForSaving::V2(runtime.clone().into()), + ), + ] { + write( + output, + name, + bincode::encode_to_vec(stored, config).unwrap(), + ); + } + let mut state = + PlatformState::default_with_protocol_versions(13, 14, &PlatformConfig::default()).unwrap(); + state.chain_lock_validating_quorums = runtime.clone().into(); + state.instant_lock_validating_quorums = runtime.into(); + write( + output, + "platform-state-v1.hex", + state.serialize_to_bytes().unwrap(), + ); + write( + output, + "checkpoint-platform-state.hex", + state.serialize_standalone_to_bytes().unwrap(), + ); + let stored = PlatformStateForSaving::V2(PlatformStateForSavingV2::from(&state)); + write( + output, + "platform-state-v2.hex", + bincode::encode_to_vec(stored, config).unwrap(), + ); +} + +fn write(output: &Path, name: &str, bytes: Vec) { + println!("{name}: {} bytes", bytes.len()); + std::fs::write(output.join(name), format!("{}\n", hex::encode(bytes))).unwrap(); +} diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v0.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v0.rs index 2800dc0683b..20ed47d60cd 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v0.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v0.rs @@ -114,7 +114,7 @@ impl From> for Quorums { QuorumHash::from_byte_array(quorum.hash.to_buffer()), VerificationQuorum { #[cfg(feature = "bls-signatures")] - public_key: dpp::bls_signatures::PublicKey::try_from( + public_key: dpp::bls::PublicKey::try_from( quorum.public_key.to_bytes().as_slice(), ) .expect("expected to convert between BLS key libraries (from chia)"), @@ -134,12 +134,86 @@ impl From> for Vec { .map(|(hash, quorum)| QuorumForSavingV0 { hash: Bytes32::from(hash.as_byte_array()), #[cfg(feature = "bls-signatures")] - public_key: bls_signatures::PublicKey::from_bytes( - &quorum.public_key.0.to_compressed(), - ) - .expect("expected to convert between BLS key libraries (to chia)"), + public_key: bls_signatures::PublicKey::from_bytes(&quorum.public_key.to_bytes()) + .expect("expected to convert between BLS key libraries (to chia)"), index: quorum.index, }) .collect() } } + +#[cfg(all(test, feature = "bls-signatures"))] +mod tests { + use super::*; + use crate::platform_types::signature_verification_quorum_set::SignatureVerificationQuorumSet; + use dpp::dashcore_rpc::json::QuorumType; + + fn assert_quorum( + quorums: &Quorums, + hash: u8, + public_key: &str, + index: Option, + ) { + let quorum = quorums + .get(&QuorumHash::from_byte_array([hash; 32])) + .unwrap(); + assert_eq!( + quorum.public_key.to_bytes().as_slice(), + hex::decode(public_key).unwrap() + ); + assert_eq!(quorum.index, index); + } + + #[test] + fn should_restore_current_and_previous_quorums_from_frozen_v0_storage() { + let bytes = hex::decode(super::super::storage_vectors::QUORUM_STORAGE_V0.trim()).unwrap(); + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + let (stored, consumed): (SignatureVerificationQuorumSetForSaving, _) = + bincode::decode_from_slice(&bytes, config).unwrap(); + assert_eq!(consumed, bytes.len()); + assert!(matches!( + stored, + SignatureVerificationQuorumSetForSaving::V0(_) + )); + assert_eq!(bincode::encode_to_vec(&stored, config).unwrap(), bytes); + + let SignatureVerificationQuorumSet::V0(restored) = stored.into(); + assert_eq!(restored.config.quorum_type, QuorumType::Llmq400_60); + assert_eq!(restored.config.active_signers, 4); + assert!(!restored.config.rotation); + assert_eq!(restored.config.window, 288); + assert_eq!(restored.current_quorums.len(), 2); + assert_quorum( + &restored.current_quorums, + 0x11, + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + None, + ); + assert_quorum( + &restored.current_quorums, + 0x22, + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + Some(0), + ); + + let previous = restored.previous.unwrap(); + assert_eq!(previous.last_active_core_height, 1000); + assert_eq!(previous.updated_at_core_height, 1008); + assert_eq!(previous.previous_change_height, Some(900)); + assert_eq!(previous.quorums.len(), 2); + assert_quorum( + &previous.quorums, + 0x33, + "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + Some(1), + ); + assert_quorum( + &previous.quorums, + 0x44, + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + None, + ); + } +} diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v1.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v1.rs index f39d8f8411f..7561f7eeae2 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v1.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/for_saving_v1.rs @@ -1,13 +1,14 @@ +use super::public_key_for_saving::PublicKeyForSaving; #[cfg(feature = "bls-signatures")] use crate::platform_types::signature_verification_quorum_set::v0::for_saving_v0::PreviousPastQuorumsForSavingV0; use crate::platform_types::signature_verification_quorum_set::v0::quorum_config_for_saving_v0::QuorumConfigForSavingV0; use crate::platform_types::signature_verification_quorum_set::v0::quorum_set::PreviousPastQuorumsV0; use crate::platform_types::signature_verification_quorum_set::{ Quorums, SignatureVerificationQuorumSetForSaving, SignatureVerificationQuorumSetV0, - ThresholdBlsPublicKey, VerificationQuorum, + VerificationQuorum, }; use bincode::{Decode, Encode}; -use dpp::bls_signatures::Bls12381G2Impl; + use dpp::dashcore::hashes::Hash; use dpp::dashcore::QuorumHash; use dpp::platform_value::Bytes32; @@ -67,8 +68,8 @@ impl From for SignatureVerificationQu #[derive(Debug, Clone, Encode, Decode)] pub struct QuorumForSavingV1 { hash: Bytes32, - #[bincode(with_serde)] - public_key: ThresholdBlsPublicKey, + // Preserve the shipped 48-byte key layout in every selecting protocol version. + public_key: PublicKeyForSaving, index: Option, } @@ -78,7 +79,7 @@ impl From> for Quorums { ( QuorumHash::from_byte_array(quorum.hash.to_buffer()), VerificationQuorum { - public_key: quorum.public_key, + public_key: quorum.public_key.0, index: quorum.index, }, ) @@ -91,7 +92,7 @@ impl From> for Vec { .into_iter() .map(|(hash, quorum)| QuorumForSavingV1 { hash: Bytes32::from(hash.as_byte_array()), - public_key: quorum.public_key, + public_key: PublicKeyForSaving(quorum.public_key), index: quorum.index, }) .collect() diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/mod.rs index bc4511bd4cf..58a17f5ba25 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/mod.rs @@ -1,6 +1,13 @@ pub mod for_saving_v0; pub mod for_saving_v1; pub mod for_saving_v2; +mod public_key_for_saving; pub mod quorum_config_for_saving_v0; pub mod quorum_set; pub mod quorums; + +#[cfg(all(test, feature = "bls-signatures"))] +mod storage_tests; + +#[cfg(all(test, feature = "bls-signatures"))] +mod storage_vectors; diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/public_key_for_saving.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/public_key_for_saving.rs new file mode 100644 index 00000000000..f35a1ecdc60 --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/public_key_for_saving.rs @@ -0,0 +1,26 @@ +use crate::platform_types::signature_verification_quorum_set::ThresholdBlsPublicKey; +use bincode::de::Decoder; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{Decode, Encode}; + +/// The disk contract is 48 compressed G1 bytes, independent of the crypto backend's Serde format. +#[derive(Debug, Clone)] +pub(super) struct PublicKeyForSaving(pub(super) ThresholdBlsPublicKey); + +impl Encode for PublicKeyForSaving { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + self.0.to_bytes().encode(encoder) + } +} + +impl Decode for PublicKeyForSaving { + fn decode>(decoder: &mut D) -> Result { + let bytes = <[u8; 48]>::decode(decoder)?; + ThresholdBlsPublicKey::try_from(bytes.as_slice()) + .map(Self) + .map_err(|_| DecodeError::Other("invalid compressed quorum public key")) + } +} + +bincode::impl_borrow_decode!(PublicKeyForSaving); diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs index 881347bc6ae..82bbab7fc46 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs @@ -303,16 +303,15 @@ impl From for SignatureVerificationQuorumSetV0 { mod tests { use super::*; use crate::config::ChainLockConfig; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey as BlsPrivateKey}; + use dpp::bls::SecretKey as BlsPrivateKey; use dpp::dashcore::hashes::Hash; use dpp::dashcore_rpc::json::QuorumType; - fn make_public_key(seed: u8) -> dpp::bls_signatures::PublicKey { + fn make_public_key(seed: u8) -> dpp::bls::PublicKey { let mut key_bytes = [0u8; 32]; key_bytes[0] = seed; key_bytes[31] = 1; - let sk = - BlsPrivateKey::::from_be_bytes(&key_bytes).expect("valid secret key"); + let sk = BlsPrivateKey::from_be_bytes(&key_bytes).expect("valid secret key"); sk.public_key() } diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs index 2781697e1dc..539de8888bb 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs @@ -1,7 +1,7 @@ use derive_more::{Deref, DerefMut, From}; -use dpp::bls_signatures; -pub use dpp::bls_signatures::PublicKey as ThresholdBlsPublicKey; -use dpp::bls_signatures::{Bls12381G2Impl, SignatureSchemes}; +use dpp::bls; +pub use dpp::bls::PublicKey as ThresholdBlsPublicKey; + use dpp::dashcore::bls_sig_utils::BLSSignature; use dpp::dashcore::{QuorumHash, Txid}; use std::collections::BTreeMap; @@ -80,7 +80,8 @@ impl Quorums { // Finalize the hash let hash_result = sha256d::Hash::from_engine(hasher); - scores.push((quorum_hash, quorum, hash_result.into())); + // Scores use raw digest bytes, without reversal, at every protocol version. + scores.push((quorum_hash, quorum, hash_result.to_byte_array())); } if scores.is_empty() { @@ -149,7 +150,7 @@ pub struct VerificationQuorum { /// Quorum threshold public key is used to verify /// signatures produced by corresponding quorum - pub public_key: ThresholdBlsPublicKey, + pub public_key: ThresholdBlsPublicKey, } impl Debug for VerificationQuorum { @@ -206,39 +207,33 @@ impl SigningQuorum { let message_digest = sha256d::Hash::from_engine(engine); - let private_key = - bls_signatures::SecretKey::::from_be_bytes(&self.private_key) - .into_option() - .ok_or(Error::BLSError( - dpp::bls_signatures::BlsError::DeserializationError( - "Could not deserialize private key".to_string(), - ), - ))?; + let private_key = bls::SecretKey::from_be_bytes(&self.private_key).ok_or( + Error::BLSError(dpp::bls::BlsError::DeserializationError( + "Could not deserialize private key".to_string(), + )), + )?; let signature = private_key - .sign( - SignatureSchemes::Basic, - message_digest.as_byte_array().as_slice(), - ) + .sign(message_digest.as_byte_array().as_slice()) .map_err(Error::BLSError)?; - Ok(BLSSignature::from(signature.as_raw_value().to_compressed())) + Ok(BLSSignature::from(signature.to_bytes())) } } #[cfg(test)] mod tests { use super::*; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey as BlsPrivateKey}; + use dpp::bls::SecretKey as BlsPrivateKey; use dpp::dashcore::hashes::Hash; use dpp::dashcore_rpc::json::QuorumType; /// Helper: generate a deterministic BLS public key from a seed byte. - fn make_public_key(seed: u8) -> ThresholdBlsPublicKey { + fn make_public_key(seed: u8) -> ThresholdBlsPublicKey { let mut key_bytes = [0u8; 32]; key_bytes[0] = seed; key_bytes[31] = 1; // ensure nonzero - let sk = BlsPrivateKey::::from_be_bytes(&key_bytes) + let sk = BlsPrivateKey::from_be_bytes(&key_bytes) .expect("expected a valid secret key from test bytes"); sk.public_key() } diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/storage_tests.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/storage_tests.rs new file mode 100644 index 00000000000..d041e873ecf --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/storage_tests.rs @@ -0,0 +1,270 @@ +use super::for_saving_v1::QuorumForSavingV1; +use super::quorum_set::PreviousPastQuorumsV0; +use super::storage_vectors; +use crate::platform_types::platform_state::platform_state_for_saving::v2::{ + deserialize_masternode_entry, deserialize_validator_set_entry, serialize_masternode_entry, + serialize_validator_set_entry, +}; +use crate::platform_types::platform_state::platform_state_for_saving::PlatformStateForSaving; +use crate::platform_types::platform_state::PlatformState; +use crate::platform_types::signature_verification_quorum_set::{ + Quorums, SignatureVerificationQuorumSet, SignatureVerificationQuorumSetForSaving, + SignatureVerificationQuorumSetV0Methods, VerificationQuorum, +}; +use dpp::bls::PublicKey; +use dpp::core_types::validator_set::ValidatorSet; +use dpp::dashcore::hashes::Hash; +use dpp::dashcore::{ProTxHash, QuorumHash}; +use dpp::dashcore_rpc::json::QuorumType; +use dpp::serialization::PlatformDeserializableFromVersionedStructureTrusted; +use dpp::version::PlatformVersion; + +const KEYS: [&str; 4] = [ + "97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "95fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48", + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", +]; + +fn config() -> bincode::config::Configuration< + bincode::config::BigEndian, + bincode::config::Varint, + bincode::config::NoLimit, +> { + bincode::config::standard() + .with_big_endian() + .with_no_limit() +} + +fn assert_quorum(quorums: &Quorums, hash: u8, key: usize, index: Option) { + let quorum = quorums + .get(&QuorumHash::from_byte_array([hash; 32])) + .unwrap(); + assert_eq!( + quorum.public_key.to_bytes().as_slice(), + hex::decode(KEYS[key]).unwrap() + ); + assert_eq!(quorum.index, index); +} + +fn assert_quorums(set: &SignatureVerificationQuorumSet) { + assert_eq!(set.config().quorum_type, QuorumType::Llmq400_60); + assert_eq!(set.config().active_signers, 4); + assert_eq!(set.config().window, 288); + assert!(!set.config().rotation); + assert_eq!(set.current_quorums().len(), 2); + assert_quorum(set.current_quorums(), 0x11, 0, None); + assert_quorum(set.current_quorums(), 0x22, 1, Some(0)); + let SignatureVerificationQuorumSet::V0(runtime) = set; + let PreviousPastQuorumsV0 { + quorums, + last_active_core_height, + updated_at_core_height, + previous_change_height, + } = runtime.previous.as_ref().unwrap(); + assert_eq!( + ( + *last_active_core_height, + *updated_at_core_height, + *previous_change_height + ), + (1000, 1008, Some(900)) + ); + assert_eq!(quorums.len(), 2); + assert_quorum(quorums, 0x33, 2, Some(1)); + assert_quorum(quorums, 0x44, 3, None); +} + +#[test] +fn should_preserve_current_and_previous_quorums_in_old_v1_and_v2_records() { + for fixture in [ + super::storage_vectors::QUORUM_STORAGE_V1, + super::storage_vectors::QUORUM_STORAGE_V2, + ] { + let bytes = hex::decode(fixture.trim()).unwrap(); + let (stored, read): (SignatureVerificationQuorumSetForSaving, _) = + bincode::decode_from_slice(&bytes, config()).unwrap(); + assert_eq!(read, bytes.len()); + assert_eq!(bincode::encode_to_vec(&stored, config()).unwrap(), bytes); + assert_quorums(&stored.into()); + } +} + +#[test] +fn should_load_old_saved_state_and_checkpoint_with_both_quorum_sets() { + for fixture in [ + super::storage_vectors::PLATFORM_STATE_V1, + super::storage_vectors::CHECKPOINT_PLATFORM_STATE, + super::storage_vectors::PLATFORM_STATE_V2, + ] { + let bytes = hex::decode(fixture.trim()).unwrap(); + let (stored, read): (PlatformStateForSaving, _) = + bincode::decode_from_slice(&bytes, config()).unwrap(); + assert_eq!(read, bytes.len()); + assert_eq!(bincode::encode_to_vec(&stored, config()).unwrap(), bytes); + let state = match stored { + PlatformStateForSaving::V2(record) => { + record.into_platform_state(vec![], vec![]).unwrap() + } + _ => PlatformState::versioned_deserialize_trusted(&bytes, PlatformVersion::latest()) + .unwrap(), + }; + assert_eq!(state.current_protocol_version_in_consensus, 13); + assert_eq!(state.next_epoch_protocol_version, 14); + assert_quorums(&state.chain_lock_validating_quorums); + assert_quorums(&state.instant_lock_validating_quorums); + if bytes[0] == 1 { + assert_eq!(state.serialize_standalone_to_bytes().unwrap(), bytes); + } + } +} + +fn saved_quorum(key: PublicKey) -> QuorumForSavingV1 { + let quorums = Quorums::from_iter([( + QuorumHash::from_byte_array([0x11; 32]), + VerificationQuorum { + public_key: key, + index: Some(1000), + }, + )]); + Vec::::from(quorums).pop().unwrap() +} + +#[test] +fn should_store_exactly_48_key_bytes_without_a_prefix_and_preserve_the_following_index() { + for key in [ + PublicKey::try_from(hex::decode(KEYS[0]).unwrap().as_slice()).unwrap(), + PublicKey::default(), + ] { + let stored = saved_quorum(key); + let mut expected = vec![0x11; 32]; + expected.extend_from_slice(&key.to_bytes()); + expected.extend_from_slice(&[1, 251, 3, 232]); // Some(1000), big-endian varint. + assert_eq!(bincode::encode_to_vec(&stored, config()).unwrap(), expected); + let (decoded, read): (QuorumForSavingV1, _) = + bincode::decode_from_slice(&expected, config()).unwrap(); + assert_eq!(read, expected.len()); + let quorums: Quorums = vec![decoded].into(); + let quorum = quorums + .get(&QuorumHash::from_byte_array([0x11; 32])) + .unwrap(); + assert_eq!(quorum.public_key, key); + assert_eq!(quorum.index, Some(1000)); + } +} + +#[test] +fn should_reject_truncated_and_invalid_stored_quorum_keys() { + let bytes = bincode::encode_to_vec(saved_quorum(PublicKey::default()), config()).unwrap(); + for len in 32..80 { + assert!( + bincode::decode_from_slice::(&bytes[..len], config()).is_err() + ); + } + let mut invalid = bytes; + invalid[32..80].fill(0); + assert!(bincode::decode_from_slice::(&invalid, config()).is_err()); +} + +fn historical_masternode_entries() -> Vec<(Vec, Vec)> { + [ + (0x31, storage_vectors::MASTERNODE_REGULAR), + (0x41, storage_vectors::MASTERNODE_EVO), + ] + .into_iter() + .map(|(tag, hex)| (vec![tag; 32], hex::decode(hex).unwrap())) + .collect() +} + +#[test] +fn should_preserve_historical_grovedb_masternode_and_validator_entries() { + for (key, bytes) in historical_masternode_entries() { + let node = deserialize_masternode_entry(&bytes).unwrap(); + assert_eq!(node.pro_tx_hash.as_byte_array().as_slice(), key); + assert_eq!(node.collateral_index, 1000); + assert_eq!(node.operator_reward, 1.5); + assert_eq!(node.state.pub_key_operator, hex::decode(KEYS[0]).unwrap()); + assert_eq!(node.state.pose_revived_height, Some(200)); + assert_eq!(node.state.pose_ban_height, None); + assert_eq!( + node.state.platform_node_id, + (key[0] == 0x41).then_some(std::array::from_fn(|i| i as u8 + 1)) + ); + assert_eq!( + serialize_masternode_entry(&node, PlatformVersion::latest()).unwrap(), + bytes + ); + } + let bytes = hex::decode(storage_vectors::VALIDATOR_SET_ENTRY).unwrap(); + let set = deserialize_validator_set_entry(&bytes).unwrap(); + assert_eq!(serialize_validator_set_entry(&set).unwrap(), bytes); + let ValidatorSet::V0(set) = set; + assert_eq!(set.quorum_hash.to_byte_array(), [0x55; 32]); + assert_eq!(set.quorum_index, Some(2)); + assert_eq!(set.core_height, 1000); + assert_eq!( + set.threshold_public_key.to_bytes().as_slice(), + hex::decode(KEYS[2]).unwrap() + ); + assert_eq!(set.members.len(), 1); + let member = set + .members + .get(&ProTxHash::from_byte_array([0x41; 32])) + .unwrap(); + assert_eq!( + member.public_key.unwrap().to_bytes().as_slice(), + hex::decode(KEYS[1]).unwrap() + ); + assert_eq!( + member.node_id.to_byte_array(), + std::array::from_fn(|i| i as u8 + 1) + ); + assert_eq!(member.node_ip, "2001:db8::1"); +} + +#[test] +fn should_restore_historical_populated_state_and_checkpoint() { + for fixture in [ + storage_vectors::POPULATED_PLATFORM_STATE_V1, + storage_vectors::POPULATED_PLATFORM_STATE_V2, + ] { + let bytes = hex::decode(fixture).unwrap(); + let (stored, read): (PlatformStateForSaving, _) = + bincode::decode_from_slice(&bytes, config()).unwrap(); + assert_eq!(read, bytes.len()); + assert_eq!(bincode::encode_to_vec(&stored, config()).unwrap(), bytes); + let state = match stored { + PlatformStateForSaving::V2(record) => record + .into_platform_state( + historical_masternode_entries(), + vec![( + vec![0x55; 32], + hex::decode(storage_vectors::VALIDATOR_SET_ENTRY).unwrap(), + )], + ) + .unwrap(), + _ => PlatformState::versioned_deserialize_trusted(&bytes, PlatformVersion::latest()) + .unwrap(), + }; + assert_quorums(&state.chain_lock_validating_quorums); + assert_quorums(&state.instant_lock_validating_quorums); + assert_eq!(state.full_masternode_list.len(), 2); + assert_eq!(state.hpmn_masternode_list.len(), 1); + assert!(state + .hpmn_masternode_list + .contains_key(&ProTxHash::from_byte_array([0x41; 32]))); + assert_eq!(state.validator_sets.len(), 1); + let set = state + .validator_sets + .get(&QuorumHash::from_byte_array([0x55; 32])) + .unwrap(); + assert_eq!( + serialize_validator_set_entry(set).unwrap(), + hex::decode(storage_vectors::VALIDATOR_SET_ENTRY).unwrap() + ); + assert_eq!( + state.serialize_standalone_to_bytes().unwrap(), + hex::decode(storage_vectors::POPULATED_CHECKPOINT_PLATFORM_STATE).unwrap() + ); + } +} diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/storage_vectors.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/storage_vectors.rs new file mode 100644 index 00000000000..09fce3a883f --- /dev/null +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/storage_vectors.rs @@ -0,0 +1,185 @@ +//! Historical bytes and provenance: see fixtures/README.md. Keep these constants frozen. + +pub(super) const CHECKPOINT_PLATFORM_STATE: &str = concat!( + "0100000d0e0000000000000000000000000000000000000000000000000000000000000000000002010400fb01200211", + "1111111111111111111111111111111111111111111111111111111111111197f1d3a73197d7942695638c4fa9ac0fc3", + "688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0022222222222222222222222222222222", + "22222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62a", + "e28f75bb8f1c7c42c39a8c5529bf0f4e0100010233333333333333333333333333333333333333333333333333333333", + "3333333395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf", + "6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444b7f1d3a73197d7942695", + "638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00fb03e8fb03f001fb03", + "8402010400fb012002111111111111111111111111111111111111111111111111111111111111111197f1d3a73197d7", + "942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00222222222222", + "2222222222222222222222222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128", + "012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e01000102333333333333333333333333333333333333", + "333333333333333333333333333395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4", + "a81e2d24daafc5387dbf6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444", + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "00fb03e8fb03f001fb0384000000", +); + +pub(super) const PLATFORM_STATE_V1: &str = concat!( + "0100000d0e0000000000000000000000000000000000000000000000000000000000000000000002010400fb01200211", + "1111111111111111111111111111111111111111111111111111111111111197f1d3a73197d7942695638c4fa9ac0fc3", + "688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0022222222222222222222222222222222", + "22222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62a", + "e28f75bb8f1c7c42c39a8c5529bf0f4e0100010233333333333333333333333333333333333333333333333333333333", + "3333333395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf", + "6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444b7f1d3a73197d7942695", + "638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00fb03e8fb03f001fb03", + "8402010400fb012002111111111111111111111111111111111111111111111111111111111111111197f1d3a73197d7", + "942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00222222222222", + "2222222222222222222222222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128", + "012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e01000102333333333333333333333333333333333333", + "333333333333333333333333333395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4", + "a81e2d24daafc5387dbf6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444", + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "00fb03e8fb03f001fb0384000000", +); + +pub(super) const PLATFORM_STATE_V2: &str = concat!( + "0200000d0e0000000000000000000000000000000000000000000000000000000000000000000002010400fb01200211", + "1111111111111111111111111111111111111111111111111111111111111197f1d3a73197d7942695638c4fa9ac0fc3", + "688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0022222222222222222222222222222222", + "22222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62a", + "e28f75bb8f1c7c42c39a8c5529bf0f4e0100010233333333333333333333333333333333333333333333333333333333", + "3333333395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf", + "6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444b7f1d3a73197d7942695", + "638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00fb03e8fb03f001fb03", + "8402010400fb012002111111111111111111111111111111111111111111111111111111111111111197f1d3a73197d7", + "942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00222222222222", + "2222222222222222222222222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128", + "012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e01000102333333333333333333333333333333333333", + "333333333333333333333333333395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4", + "a81e2d24daafc5387dbf6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444", + "b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "00fb03e8fb03f001fb038400", +); + +pub(super) const QUORUM_STORAGE_V0: &str = concat!( + "00010400fb01200211111111111111111111111111111111111111111111111111111111111111113097f1d3a73197d7", + "942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00222222222222", + "222222222222222222222222222222222222222222222222222230a572cbea904d67468808c8eb50a9450c9721db3091", + "28012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e010001023333333333333333333333333333333333", + "3333333333333333333333333333333095fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea1137", + "79c4a81e2d24daafc5387dbf6ac5fe480101444444444444444444444444444444444444444444444444444444444444", + "444430b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb", + "22c6bb00fb03e8fb03f001fb0384", +); + +pub(super) const QUORUM_STORAGE_V1: &str = concat!( + "01010400fb012002111111111111111111111111111111111111111111111111111111111111111197f1d3a73197d794", + "2695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0022222222222222", + "22222222222222222222222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db30912801", + "2543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e0100010233333333333333333333333333333333333333", + "333333333333333333333333333095fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4", + "a81e2d24daafc5387dbf6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444", + "30b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6", + "bb00fb03e8fb03f001fb0384", +); + +pub(super) const QUORUM_STORAGE_V2: &str = concat!( + "02010400fb012002111111111111111111111111111111111111111111111111111111111111111197f1d3a73197d794", + "2695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0022222222222222", + "22222222222222222222222222222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db30912801", + "2543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e0100010233333333333333333333333333333333333333", + "3333333333333333333333333395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a8", + "1e2d24daafc5387dbf6ac5fe4801014444444444444444444444444444444444444444444444444444444444444444b7", + "f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00", + "fb03e8fb03f001fb0384", +); + +// Generated with Platform 6499c680c6bc311e8933f396423a79459e2a88bd (before #5307). + +pub(super) const MASTERNODE_REGULAR: &str = concat!( + "000020313131313131313131313131313131313131313131313131313131313131313120323232323232323232323232", + "3232323232323232323232323232323232323232fb03e821212121212121212121212121212121212121213fc0000000", + "01020304fb4e1f6401c80000222222222222222222222222222222222222222223232323232323232323232323232323", + "2323232324242424242424242424242424242424242424243097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b9", + "05a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0125252525252525252525252525252525252525250000", + "00", +); + +pub(super) const MASTERNODE_EVO: &str = concat!( + "000120414141414141414141414141414141414141414141414141414141414141414120424242424242424242424242", + "4242424242424242424242424242424242424242fb03e821212121212121212121212121212121212121213fc0000001", + "20010db8000000000000000000000001fb4e1f6401c80000222222222222222222222222222222222222222223232323", + "2323232323232323232323232323232324242424242424242424242424242424242424243097f1d3a73197d794269563", + "8c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0125252525252525252525", + "25252525252525252525010102030405060708090a0b0c0d0e0f101112131401fb682001fb01bb", +); + +pub(super) const VALIDATOR_SET_ENTRY: &str = concat!( + "0055555555555555555555555555555555555555555555555555555555555555550102fb03e801204141414141414141", + "414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141", + "414141414141414101a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c", + "42c39a8c5529bf0f4e0b323030313a6462383a3a310102030405060708090a0b0c0d0e0f1011121314fb4e1ffb01bbfb", + "68200095fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6a", + "c5fe48", +); + +pub(super) const POPULATED_PLATFORM_STATE_V1: &str = concat!( + "0100000d0e55555555555555555555555555555555555555555555555555555555555555550001555555555555555555", + "555555555555555555555555555555555555555555555500555555555555555555555555555555555555555555555555", + "55555555555555550102fb03e80120414141414141414141414141414141414141414141414141414141414141414141", + "4141414141414141414141414141414141414141414141414141414141414101a572cbea904d67468808c8eb50a9450c", + "9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e0b323030313a6462383a3a3101020304", + "05060708090a0b0c0d0e0f1011121314fb4e1ffb01bbfb68200095fde78acd5f6886ddaf5d0056610167c513d09c1c0e", + "fabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe4802010400fb0120021111111111111111111111111111", + "11111111111111111111111111111111111197f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171b", + "ac586c55e83ff97a1aeffb3af00adb22c6bb002222222222222222222222222222222222222222222222222222222222", + "222222a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529", + "bf0f4e01000102333333333333333333333333333333333333333333333333333333333333333395fde78acd5f6886dd", + "af5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48010144444444444444", + "44444444444444444444444444444444444444444444444444b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b9", + "05a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00fb03e8fb03f001fb038402010400fb01200211111111", + "1111111111111111111111111111111111111111111111111111111197f1d3a73197d7942695638c4fa9ac0fc3688c4f", + "9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0022222222222222222222222222222222222222", + "22222222222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75", + "bb8f1c7c42c39a8c5529bf0f4e0100010233333333333333333333333333333333333333333333333333333333333333", + "3395fde78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe", + "4801014444444444444444444444444444444444444444444444444444444444444444b7f1d3a73197d7942695638c4f", + "a9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00fb03e8fb03f001fb03840231", + "313131313131313131313131313131313131313131313131313131313131310000203131313131313131313131313131", + "313131313131313131313131313131313131203232323232323232323232323232323232323232323232323232323232", + "323232fb03e821212121212121212121212121212121212121213fc000000001020304fb4e1f6401c800002222222222", + "222222222222222222222222222222232323232323232323232323232323232323232324242424242424242424242424", + "242424242424243097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aef", + "fb3af00adb22c6bb01252525252525252525252525252525252525252500000041414141414141414141414141414141", + "414141414141414141414141414141410001204141414141414141414141414141414141414141414141414141414141", + "414141204242424242424242424242424242424242424242424242424242424242424242fb03e8212121212121212121", + "21212121212121212121213fc000000120010db8000000000000000000000001fb4e1f6401c800002222222222222222", + "222222222222222222222222232323232323232323232323232323232323232324242424242424242424242424242424", + "242424243097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af0", + "0adb22c6bb012525252525252525252525252525252525252525010102030405060708090a0b0c0d0e0f101112131401", + "fb682001fb01bb0141414141414141414141414141414141414141414141414141414141414141410001204141414141", + "414141414141414141414141414141414141414141414141414141204242424242424242424242424242424242424242", + "424242424242424242424242fb03e821212121212121212121212121212121212121213fc000000120010db800000000", + "0000000000000001fb4e1f6401c800002222222222222222222222222222222222222222232323232323232323232323", + "232323232323232324242424242424242424242424242424242424243097f1d3a73197d7942695638c4fa9ac0fc3688c", + "4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb01252525252525252525252525252525252525", + "2525010102030405060708090a0b0c0d0e0f101112131401fb682001fb01bb00", +); + +pub(super) const POPULATED_PLATFORM_STATE_V2: &str = concat!( + "0200000d0e55555555555555555555555555555555555555555555555555555555555555550001555555555555555555", + "555555555555555555555555555555555555555555555502010400fb0120021111111111111111111111111111111111", + "11111111111111111111111111111197f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c", + "55e83ff97a1aeffb3af00adb22c6bb002222222222222222222222222222222222222222222222222222222222222222", + "a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e", + "01000102333333333333333333333333333333333333333333333333333333333333333395fde78acd5f6886ddaf5d00", + "56610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe48010144444444444444444444", + "44444444444444444444444444444444444444444444b7f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e", + "3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00fb03e8fb03f001fb038402010400fb01200211111111111111", + "1111111111111111111111111111111111111111111111111197f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b9", + "05a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0022222222222222222222222222222222222222222222", + "22222222222222222222a572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c", + "7c42c39a8c5529bf0f4e01000102333333333333333333333333333333333333333333333333333333333333333395fd", + "e78acd5f6886ddaf5d0056610167c513d09c1c0efabbc7cdcc69beea113779c4a81e2d24daafc5387dbf6ac5fe480101", + "4444444444444444444444444444444444444444444444444444444444444444b7f1d3a73197d7942695638c4fa9ac0f", + "c3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00fb03e8fb03f001fb038400", +); + +// Both historical writers produce identical bytes. +pub(super) const POPULATED_CHECKPOINT_PLATFORM_STATE: &str = POPULATED_PLATFORM_STATE_V1; diff --git a/packages/rs-drive-abci/src/platform_types/validator/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/validator/v0/mod.rs index 2fbab063bd1..810bb7013cb 100644 --- a/packages/rs-drive-abci/src/platform_types/validator/v0/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/validator/v0/mod.rs @@ -1,6 +1,6 @@ use crate::platform_types::platform_state::PlatformState; use crate::platform_types::platform_state::PlatformStateV0Methods; -use dpp::bls_signatures::{Bls12381G2Impl, PublicKey as BlsPublicKey}; +use dpp::bls::PublicKey as BlsPublicKey; pub use dpp::core_types::validator::v0::*; use dpp::dashcore::hashes::Hash; use dpp::dashcore::{ProTxHash, PubkeyHash}; @@ -8,7 +8,7 @@ use dpp::dashcore_rpc::json::{DMNState, MasternodeListItem}; pub(crate) trait NewValidatorIfMasternodeInState { fn new_validator_if_masternode_in_state( pro_tx_hash: ProTxHash, - public_key: Option>, + public_key: Option, state: &PlatformState, ) -> Option; } @@ -17,7 +17,7 @@ impl NewValidatorIfMasternodeInState for ValidatorV0 { /// Makes a validator if the masternode is in the list and is valid fn new_validator_if_masternode_in_state( pro_tx_hash: ProTxHash, - public_key: Option>, + public_key: Option, state: &PlatformState, ) -> Option { let MasternodeListItem { state, .. } = state.hpmn_masternode_list().get(&pro_tx_hash)?; diff --git a/packages/rs-drive-abci/src/platform_types/validator_set/v0/mod.rs b/packages/rs-drive-abci/src/platform_types/validator_set/v0/mod.rs index 4d639f64c1a..60fe3d57a3c 100644 --- a/packages/rs-drive-abci/src/platform_types/validator_set/v0/mod.rs +++ b/packages/rs-drive-abci/src/platform_types/validator_set/v0/mod.rs @@ -6,7 +6,7 @@ use dpp::dashcore::{ProTxHash, PubkeyHash}; use crate::platform_types::platform_state::PlatformState; use crate::platform_types::validator::v0::NewValidatorIfMasternodeInState; -use dpp::bls_signatures::PublicKey as BlsPublicKey; +use dpp::bls::PublicKey as BlsPublicKey; use dpp::core_types::validator::v0::ValidatorV0; pub use dpp::core_types::validator_set::v0::*; use dpp::dashcore_rpc::json::QuorumInfoResult; @@ -58,8 +58,8 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { return Err(Error::Execution(ExecutionError::CorruptedCachedState( format!( "updating validator set doesn't match threshold public key ours: {} theirs: {}", - hex::encode(self.threshold_public_key.0.to_compressed()), - hex::encode(rhs.threshold_public_key.0.to_compressed()) + hex::encode(self.threshold_public_key.to_bytes()), + hex::encode(rhs.threshold_public_key.to_bytes()) ), ))); } @@ -97,7 +97,7 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { Some(Ok(abci::ValidatorUpdate { pub_key: (*public_key).map(|public_key| crypto::PublicKey { - sum: Some(Bls12381(public_key.0.to_compressed().to_vec())), + sum: Some(Bls12381(public_key.to_bytes().to_vec())), }), power: 100, pro_tx_hash: pro_tx_hash.as_byte_array().to_vec(), @@ -123,7 +123,7 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { Some(Ok(abci::ValidatorUpdate { pub_key: (*public_key).map(|public_key| crypto::PublicKey { - sum: Some(Bls12381(public_key.0.to_compressed().to_vec())), + sum: Some(Bls12381(public_key.to_bytes().to_vec())), }), power: 100, pro_tx_hash: pro_tx_hash.to_byte_array().to_vec(), @@ -139,9 +139,7 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { Ok(ValidatorSetUpdate { validator_updates, threshold_public_key: Some(crypto::PublicKey { - sum: Some(Bls12381( - self.threshold_public_key.0.to_compressed().to_vec(), - )), + sum: Some(Bls12381(self.threshold_public_key.to_bytes().to_vec())), }), quorum_hash: self.quorum_hash.to_byte_array().to_vec(), }) @@ -174,7 +172,7 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { let node_address = validator_node_address(node_id, node_ip, *platform_p2p_port); Some(abci::ValidatorUpdate { pub_key: public_key.as_ref().map(|public_key| crypto::PublicKey { - sum: Some(Bls12381(public_key.0.to_compressed().to_vec())), + sum: Some(Bls12381(public_key.to_bytes().to_vec())), }), power: 100, pro_tx_hash: pro_tx_hash.to_byte_array().to_vec(), @@ -183,7 +181,7 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { }) .collect(), threshold_public_key: Some(crypto::PublicKey { - sum: Some(Bls12381(threshold_public_key.0.to_compressed().to_vec())), + sum: Some(Bls12381(threshold_public_key.to_bytes().to_vec())), }), quorum_hash: quorum_hash.to_byte_array().to_vec(), } @@ -217,7 +215,7 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { Some(abci::ValidatorUpdate { pub_key: public_key.map(|public_key| crypto::PublicKey { - sum: Some(Bls12381(public_key.0.to_compressed().to_vec())), + sum: Some(Bls12381(public_key.to_bytes().to_vec())), }), power: 100, pro_tx_hash: pro_tx_hash.to_byte_array().to_vec(), @@ -226,7 +224,7 @@ impl ValidatorSetMethodsV0 for ValidatorSetV0 { }) .collect(), threshold_public_key: Some(crypto::PublicKey { - sum: Some(Bls12381(threshold_public_key.0.to_compressed().to_vec())), + sum: Some(Bls12381(threshold_public_key.to_bytes().to_vec())), }), quorum_hash: quorum_hash.to_byte_array().to_vec(), } @@ -315,7 +313,7 @@ fn validator_node_address(node_id: &PubkeyHash, node_ip: &str, platform_p2p_port #[cfg(test)] mod tests { use super::*; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey}; + use dpp::bls::SecretKey; use dpp::dashcore::hashes::Hash; use dpp::dashcore::{ProTxHash, PubkeyHash, QuorumHash}; use rand::rngs::StdRng; @@ -323,7 +321,7 @@ mod tests { fn make_validator(pro_tx_hash: ProTxHash, is_banned: bool) -> ValidatorV0 { let mut rng = StdRng::seed_from_u64(1); - let public_key = Some(SecretKey::::random(&mut rng).public_key()); + let public_key = Some(SecretKey::random(&mut rng).public_key()); ValidatorV0 { pro_tx_hash, public_key, @@ -343,7 +341,7 @@ mod tests { members: BTreeMap, ) -> ValidatorSetV0 { let mut rng = StdRng::seed_from_u64(threshold_seed); - let threshold_public_key = SecretKey::::random(&mut rng).public_key(); + let threshold_public_key = SecretKey::random(&mut rng).public_key(); ValidatorSetV0 { quorum_hash: QuorumHash::from_slice(&[quorum_hash_seed; 32]).unwrap(), quorum_index: Some(1), diff --git a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs index cf2f59b6f15..3f0ac25dda1 100644 --- a/packages/rs-drive-abci/src/query/document_query/v1/tests.rs +++ b/packages/rs-drive-abci/src/query/document_query/v1/tests.rs @@ -3933,7 +3933,7 @@ mod having_trust_boundary { use crate::rpc::core::MockCoreRPCLike; use dapi_grpc::platform::v0::{Proof, ResponseMetadata}; use dpp::block::block_info::BlockInfo; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey, SignatureSchemes}; + use dpp::bls::SecretKey; use dpp::dashcore::Network; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; @@ -4024,12 +4024,10 @@ mod having_trust_boundary { } /// A deterministic, valid BLS scalar — no RNG dependency. - pub(super) fn quorum_secret_key() -> SecretKey { + pub(super) fn quorum_secret_key() -> SecretKey { let mut bytes = [0u8; 32]; bytes[31] = 42; - SecretKey::::from_be_bytes(&bytes) - .into_option() - .expect("a small nonzero scalar is a valid secret key") + SecretKey::from_be_bytes(&bytes).expect("a small nonzero scalar is a valid secret key") } fn register_grades( @@ -4192,7 +4190,7 @@ mod having_trust_boundary { grovedb_proof: Vec, app_hash: &[u8; 32], mtd: &ResponseMetadata, - secret_key: &SecretKey, + secret_key: &SecretKey, quorum_hash: [u8; 32], ) -> Proof { let block_id_hash = [7u8; 32].to_vec(); @@ -4224,10 +4222,9 @@ mod having_trust_boundary { ) .expect("sign digest"); let signature = secret_key - .sign(SignatureSchemes::Basic, &sign_digest) + .sign(&sign_digest) .expect("signing with a valid key succeeds") - .as_raw_value() - .to_compressed() + .to_bytes() .to_vec(); Proof { grovedb_proof, @@ -4254,7 +4251,7 @@ mod having_trust_boundary { let mtd = metadata(); let proof = signed_proof(grovedb_proof, &root_hash, &mtd, &secret_key, quorum_hash); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let query = client_side_query(&contract); @@ -4291,7 +4288,7 @@ mod having_trust_boundary { quorum_hash, ); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let query = client_side_query(&contract); @@ -4316,7 +4313,7 @@ mod having_trust_boundary { let mtd = metadata(); let proof = signed_proof(grovedb_proof, &root_hash, &mtd, &secret_key, quorum_hash); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let mut tampered = mtd; @@ -4347,11 +4344,9 @@ mod having_trust_boundary { let mut other_bytes = [0u8; 32]; other_bytes[31] = 43; - let other_key = SecretKey::::from_be_bytes(&other_bytes) - .into_option() - .expect("valid scalar"); + let other_key = SecretKey::from_be_bytes(&other_bytes).expect("valid scalar"); let provider = TestQuorumProvider { - pubkey: other_key.public_key().0.to_compressed(), + pubkey: other_key.public_key().to_bytes(), }; let query = client_side_query(&contract); @@ -4665,7 +4660,7 @@ mod time_range_proof_verification { QUORUM_HASH, ); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; (signed, mtd, provider) } @@ -6711,7 +6706,7 @@ mod chained_trust_boundary { let mtd = metadata(); let proof = signed_proof(grovedb_proof, &root_hash, &mtd, &secret_key, quorum_hash); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let (verified, _mtd, _proof) = @@ -6754,14 +6749,10 @@ mod chained_trust_boundary { let other_key = { let mut bytes = [0u8; 32]; bytes[31] = 43; - dpp::bls_signatures::SecretKey::::from_be_bytes( - &bytes, - ) - .into_option() - .expect("valid scalar") + dpp::bls::SecretKey::from_be_bytes(&bytes).expect("valid scalar") }; let provider = TestQuorumProvider { - pubkey: other_key.public_key().0.to_compressed(), + pubkey: other_key.public_key().to_bytes(), }; let refused = @@ -6788,7 +6779,7 @@ mod chained_trust_boundary { let mtd = metadata(); let proof = signed_proof(grovedb_proof, &root_hash, &mtd, &secret_key, [5u8; 32]); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let mut tampered = mtd; tampered.height += 1; @@ -7048,7 +7039,7 @@ mod composite_trust_boundary { let mtd = metadata(); let proof = signed_proof(grovedb_proof, &root_hash, &mtd, &secret_key, quorum_hash); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let response = response_with(proof, mtd); @@ -7205,14 +7196,10 @@ mod composite_trust_boundary { let other_key = { let mut bytes = [0u8; 32]; bytes[31] = 44; - dpp::bls_signatures::SecretKey::::from_be_bytes( - &bytes, - ) - .into_option() - .expect("valid scalar") + dpp::bls::SecretKey::from_be_bytes(&bytes).expect("valid scalar") }; let provider = TestQuorumProvider { - pubkey: other_key.public_key().0.to_compressed(), + pubkey: other_key.public_key().to_bytes(), }; let refused = @@ -7240,7 +7227,7 @@ mod composite_trust_boundary { let mtd = metadata(); let proof = signed_proof(grovedb_proof, &root_hash, &mtd, &secret_key, [6u8; 32]); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let mut tampered = mtd; tampered.height += 1; @@ -7285,7 +7272,7 @@ mod composite_trust_boundary { let mtd = metadata(); let proof = signed_proof(grovedb_proof, &root_hash, &mtd, &secret_key, [6u8; 32]); let provider = TestQuorumProvider { - pubkey: secret_key.public_key().0.to_compressed(), + pubkey: secret_key.public_key().to_bytes(), }; let refused = diff --git a/packages/rs-drive-abci/src/query/system/current_quorums_info/v0/mod.rs b/packages/rs-drive-abci/src/query/system/current_quorums_info/v0/mod.rs index ee6fa018a85..b5c684c80f1 100644 --- a/packages/rs-drive-abci/src/query/system/current_quorums_info/v0/mod.rs +++ b/packages/rs-drive-abci/src/query/system/current_quorums_info/v0/mod.rs @@ -41,11 +41,7 @@ impl Platform { quorum_hash: validator_set.quorum_hash().as_byte_array().to_vec(), core_height: validator_set.core_height(), members, - threshold_public_key: validator_set - .threshold_public_key() - .0 - .to_compressed() - .to_vec(), + threshold_public_key: validator_set.threshold_public_key().to_bytes().to_vec(), } }) .collect(); diff --git a/packages/rs-drive-abci/src/rpc/core.rs b/packages/rs-drive-abci/src/rpc/core.rs index 749936920c7..8d3346a310d 100644 --- a/packages/rs-drive-abci/src/rpc/core.rs +++ b/packages/rs-drive-abci/src/rpc/core.rs @@ -25,13 +25,8 @@ const COINBASE_TRANSACTION_TYPE: u16 = 5; /// coinbase transaction; `0` when it carries no payload or its payload predates version 3, /// before the credit pool existed, which is how Core's own unlock limit reads such a block. /// -/// Decoded with `deserialize_partial`: the pinned payload decoder reads the fields of version 3 -/// for every later version and stops after the balance, while the version 4 payload Core v24 -/// requires appends `merkleRootAssetUnlocks` after it. A strict `deserialize`, and so a whole -/// `Block` decode, refuses those unread bytes. Core has only ever appended fields to the -/// payload, and its consensus rules (`CheckCbTx`) refuse versions it does not know, so the -/// balance stays where version 3 put it unless a Core release moves it, which Platform would -/// have to follow anyway. +/// Uses `deserialize_partial` to tolerate appended payload fields after those understood +/// by the decoder. The credit-pool balance occupies the same field from version 3 onward. pub(crate) fn credit_pool_balance_from_coinbase(coinbase: &[u8]) -> Result { let (transaction, _) = deserialize_partial::(coinbase) .map_err(|e| format!("coinbase cannot be decoded: {e}"))?; @@ -487,6 +482,7 @@ mod tests { best_cl_height: Some(30), best_cl_signature: Some(BLSSignature::from([3; 96])), asset_locked_amount: Some(BALANCE_DUFFS), + merkle_root_asset_unlocks: None, }) } @@ -499,7 +495,7 @@ mod tests { } /// Core v24 blocks carry a version 4 payload, which appends `merkleRootAssetUnlocks` - /// after the balance; the pinned transaction decoder cannot read it. + /// after the balance. #[test] fn should_read_the_balance_of_a_version_4_coinbase() { let version_3 = coinbase(Some(version_3_payload())); @@ -528,6 +524,7 @@ mod tests { best_cl_height: None, best_cl_signature: None, asset_locked_amount: None, + merkle_root_asset_unlocks: None, }); assert_eq!( credit_pool_balance_from_coinbase(&coinbase(Some(version_2))), diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs index 6d25d111b30..e0f3b4fac3e 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/execution.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs @@ -19,7 +19,7 @@ use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use strategy_tests::operations::FinalizeBlockOperation::IdentityAddKeys; -use dpp::bls_signatures::{Bls12381G2Impl, SecretKey as BlsPrivateKey, SignatureSchemes}; +use dpp::bls::SecretKey as BlsPrivateKey; use dpp::dashcore::consensus::Encodable; use dpp::dashcore::hashes::{sha256d, HashEngine}; use dpp::dashcore::{ChainLock, QuorumSigningRequestId, VarInt}; @@ -733,19 +733,15 @@ pub(crate) async fn run_chain_for_strategy<'a>( let message_digest = sha256d::Hash::from_engine(engine); - let quorum_private_key = - BlsPrivateKey::::from_be_bytes(quorum_private_key) - .expect("expected to have a valid private key"); + let quorum_private_key = BlsPrivateKey::from_be_bytes(quorum_private_key) + .expect("expected to have a valid private key"); let signature = quorum_private_key - .sign( - SignatureSchemes::Basic, - message_digest.as_byte_array().as_slice(), - ) + .sign(message_digest.as_byte_array().as_slice()) .expect("expected to sign"); let chain_lock = ChainLock { block_height, block_hash: BlockHash::from_byte_array(*block_hash), - signature: signature.as_raw_value().to_compressed().into(), + signature: signature.to_bytes().into(), }; Ok(chain_lock) @@ -860,9 +856,7 @@ pub(crate) async fn start_chain_for_strategy<'a>( .map( |validator_in_quorum| tenderdash_abci::proto::abci::ValidatorUpdate { pub_key: Some(tenderdash_abci::proto::crypto::PublicKey { - sum: Some(Bls12381( - validator_in_quorum.public_key.0.to_compressed().to_vec(), - )), + sum: Some(Bls12381(validator_in_quorum.public_key.to_bytes().to_vec())), }), power: 100, pro_tx_hash: validator_in_quorum.pro_tx_hash.to_byte_array().to_vec(), @@ -872,11 +866,7 @@ pub(crate) async fn start_chain_for_strategy<'a>( .collect(), threshold_public_key: Some(tenderdash_abci::proto::crypto::PublicKey { sum: Some(Bls12381( - current_quorum_with_test_info - .public_key - .0 - .to_compressed() - .to_vec(), + current_quorum_with_test_info.public_key.to_bytes().to_vec(), )), }), quorum_hash: current_validator_quorum_hash.to_byte_array().to_vec(), diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs index 755fb41b856..10854de9c87 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/main.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs @@ -34,7 +34,7 @@ #![allow(unused_variables)] extern crate core; -use dpp::bls_signatures::SecretKey as BlsPrivateKey; +use dpp::bls::SecretKey as BlsPrivateKey; mod addresses_with_balance; mod execution; mod failures; diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs index 3ada8f30353..c9dbe379d11 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/masternode_list_item_helpers.rs @@ -1,5 +1,5 @@ use crate::BlsPrivateKey; -use dpp::bls_signatures::Bls12381G2Impl; + use dpp::dashcore_rpc::json::MasternodeListItem; use rand::prelude::IteratorRandom; use rand::rngs::StdRng; @@ -39,12 +39,13 @@ impl UpdateMasternodeListItem for MasternodeListItem { .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( - &private_key_operator_bytes.try_into().expect("expected the secret key to be 32 bytes"), + let private_key_operator = BlsPrivateKey::from_be_bytes( + &private_key_operator_bytes + .try_into() + .expect("expected the secret key to be 32 bytes"), ) - .expect("expected the conversion between bls signatures library and blsful to happen without failing"); - let pub_key_operator = - private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); self.state.pub_key_operator = pub_key_operator; } 4 => { @@ -97,11 +98,13 @@ mod tests { .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( - &private_key_operator_bytes.try_into().expect("expected the secret key to be 32 bytes"), + let private_key_operator = BlsPrivateKey::from_be_bytes( + &private_key_operator_bytes + .try_into() + .expect("expected the secret key to be 32 bytes"), ) - .expect("expected the conversion between bls signatures library and blsful to happen without failing"); - let pub_key_operator = private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); let masternode_list_item = MasternodeListItem { node_type: MasternodeType::Regular, pro_tx_hash, diff --git a/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs index 60d53f8fabc..10b786ded31 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/masternodes.rs @@ -1,5 +1,5 @@ use crate::masternode_list_item_helpers::UpdateMasternodeListItem; -use dpp::bls_signatures::{Bls12381G2Impl, SecretKey as BlsPrivateKey}; +use dpp::bls::SecretKey as BlsPrivateKey; use dpp::dashcore::hashes::Hash; use dpp::dashcore::{ProTxHash, QuorumHash, Txid}; use dpp::dashcore_rpc::dashcore_rpc_json::{DMNState, MasternodeListItem, MasternodeType}; @@ -233,11 +233,13 @@ pub fn generate_test_masternodes( .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( - &private_key_operator_bytes.try_into().expect("expected the secret key to be 32 bytes"), + let private_key_operator = BlsPrivateKey::from_be_bytes( + &private_key_operator_bytes + .try_into() + .expect("expected the secret key to be 32 bytes"), ) - .expect("expected the conversion between bls signatures library and blsful to happen without failing"); - let pub_key_operator = private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); let pro_tx_hash = ProTxHash::from_byte_array(rng.gen::<[u8; 32]>()); let masternode_list_item = MasternodeListItem { node_type: MasternodeType::Regular, @@ -375,11 +377,13 @@ pub fn generate_test_masternodes( .expect("expected to generate a private key") .to_bytes() .to_vec(); - let private_key_operator = BlsPrivateKey::::from_be_bytes( - &private_key_operator_bytes.try_into().expect("expected the secret key to be 32 bytes"), + let private_key_operator = BlsPrivateKey::from_be_bytes( + &private_key_operator_bytes + .try_into() + .expect("expected the secret key to be 32 bytes"), ) - .expect("expected the conversion between bls signatures library and blsful to happen without failing"); - let pub_key_operator = private_key_operator.public_key().0.to_compressed().to_vec(); + .expect("Core fixture secret key must decode"); + let pub_key_operator = private_key_operator.public_key().to_bytes().to_vec(); let masternode_list_item = MasternodeListItem { node_type: MasternodeType::Evo, pro_tx_hash: ProTxHash::from_byte_array(rng.gen::<[u8; 32]>()), diff --git a/packages/rs-drive-abci/tests/strategy_tests/query.rs b/packages/rs-drive-abci/tests/strategy_tests/query.rs index a7b89ac350c..224a81325e9 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/query.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/query.rs @@ -9,7 +9,7 @@ use dapi_grpc::platform::v0::{ get_identity_by_public_key_hash_request, get_identity_by_public_key_hash_response, GetIdentityByPublicKeyHashRequest, Proof, }; -use dpp::bls_signatures::{Bls12381G2Impl, BlsError, Pairing, Signature}; +use dpp::bls::{BlsError, Signature}; use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType; use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; @@ -74,7 +74,7 @@ pub struct ProofVerification<'a> { pub signature: &'a [u8; 96], /// Threshold key used to verify the signature - pub public_key: &'a dpp::bls_signatures::PublicKey, + pub public_key: &'a dpp::bls::PublicKey, } impl ProofVerification<'_> { @@ -111,20 +111,17 @@ impl ProofVerification<'_> { Err(e) => return SimpleValidationResult::new_with_error(e.into()), }; // We could have received a fake commit, so signature validation needs to be returned if error as a simple validation result - let signature = - match ::Signature::from_compressed(self.signature) - .into_option() - { - Some(signature) => Signature::Basic(signature), - None => { - return SimpleValidationResult::new_with_error( - AbciError::BlsErrorOfTenderdashThresholdMechanism( - BlsError::InvalidSignature, - format!("malformed signature data: {}", hex::encode(self.signature)), - ), - ); - } - }; + let signature = match Signature::from_compressed(self.signature) { + Some(signature) => signature, + None => { + return SimpleValidationResult::new_with_error( + AbciError::BlsErrorOfTenderdashThresholdMechanism( + BlsError::InvalidSignature, + format!("malformed signature data: {}", hex::encode(self.signature)), + ), + ); + } + }; tracing::trace!( digest=hex::encode(&digest), ?state_id, diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs index 96820a70270..16dba17f478 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs @@ -1699,7 +1699,7 @@ impl NetworkStrategy { let (pro_tx_hash, masternode_list_item) = full_masternode_list.iter().nth(rand_index).unwrap(); - let pro_tx_hash_bytes: [u8; 32] = pro_tx_hash.to_raw_hash().into(); + let pro_tx_hash_bytes: [u8; 32] = pro_tx_hash.to_byte_array(); let voting_address = masternode_list_item.state.voting_address; let voting_identifier = Identifier::create_voter_identifier( @@ -2246,7 +2246,7 @@ impl NetworkStrategy { IdentityTopUpTransition::try_from_identity_with_private_key( identity, asset_lock_proof, - secret_key.as_ref(), + secret_key.as_secret_bytes(), 0, platform_version, None, @@ -2320,7 +2320,7 @@ impl NetworkStrategy { ( asset_lock_proof, - secret_key.secret_bytes().to_vec(), + secret_key.to_secret_bytes().to_vec(), funded_amount, ) } diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs index e048c50ba76..ef3b61472ba 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/address_tests.rs @@ -75,12 +75,7 @@ mod tests { .iter() .map(|(quorum_hash, quorum_info)| { let quorum_hash_bytes: [u8; 32] = *quorum_hash.as_raw_hash().as_byte_array(); - let public_key_bytes: [u8; 48] = quorum_info - .public_key - .0 - .to_compressed() - .try_into() - .expect("public key should be 48 bytes"); + let public_key_bytes: [u8; 48] = quorum_info.public_key.to_bytes(); ((quorum_hash_bytes, quorum_type), public_key_bytes) }) .collect(); diff --git a/packages/rs-drive-proof-verifier/src/proof/token_direct_purchase.rs b/packages/rs-drive-proof-verifier/src/proof/token_direct_purchase.rs index 45c70a62aff..4a44bc7ce69 100644 --- a/packages/rs-drive-proof-verifier/src/proof/token_direct_purchase.rs +++ b/packages/rs-drive-proof-verifier/src/proof/token_direct_purchase.rs @@ -9,10 +9,7 @@ use dapi_grpc::platform::{ }, VersionedGrpcResponse, }; -use dpp::{ - dashcore::{secp256k1::hashes::hex::DisplayHex, Network}, - version::PlatformVersion, -}; +use dpp::{dashcore::Network, version::PlatformVersion}; use drive::drive::Drive; use crate::{ @@ -51,7 +48,7 @@ impl FromProof for TokenDirectPurchasePrice .map(<[u8; 32]>::try_from) .collect::, _>>() // BTreeSet to make it unique .map_err(|e| Error::RequestError { - error: format!("token id {} has invalid length", e.to_lower_hex_string()), + error: format!("token id {} has invalid length", hex::encode(e)), })? .into_iter() .collect::>(); diff --git a/packages/rs-drive-proof-verifier/src/unproved.rs b/packages/rs-drive-proof-verifier/src/unproved.rs index fde99cc3399..e2a7aeea126 100644 --- a/packages/rs-drive-proof-verifier/src/unproved.rs +++ b/packages/rs-drive-proof-verifier/src/unproved.rs @@ -24,7 +24,7 @@ use dapi_grpc::platform::v0::ResponseMetadata; use dapi_grpc::platform::v0::{self as platform}; use dapi_grpc::platform::v0::get_contract_moderation_action_counts_response::get_contract_moderation_action_counts_response_v0::Result as ModerationActionCountsResult; use dapi_grpc::tonic::async_trait; -use dpp::bls_signatures::PublicKey as BlsPublicKey; +use dpp::bls::PublicKey as BlsPublicKey; use dpp::core_types::validator::v0::ValidatorV0; use dpp::core_types::validator_set::v0::ValidatorSetV0; use dpp::core_types::validator_set::ValidatorSet; @@ -315,7 +315,7 @@ mod tests { use dapi_grpc::platform::v0::{ get_current_quorums_info_response, get_status_response, ResponseMetadata, }; - use dpp::bls_signatures::{Bls12381G2Impl, SecretKey}; + use dpp::bls::SecretKey; use dpp::version::PlatformVersion; /// Generate a valid BLS public key as compressed bytes (48 bytes) from a @@ -323,11 +323,8 @@ mod tests { fn generate_valid_bls_public_key_bytes(seed: u8) -> Vec { let mut secret_bytes = [0u8; 32]; secret_bytes[31] = seed.max(1); // ensure nonzero - let sk: SecretKey = - SecretKey::::from_be_bytes(&secret_bytes) - .into_option() - .expect("valid secret key"); - sk.public_key().0.to_compressed().to_vec() + let sk: SecretKey = SecretKey::from_be_bytes(&secret_bytes).expect("valid secret key"); + sk.public_key().to_bytes().to_vec() } /// Helper: build a valid GetCurrentQuorumsInfoResponse with one quorum hash, diff --git a/packages/rs-drive-proof-verifier/src/verify.rs b/packages/rs-drive-proof-verifier/src/verify.rs index 13e5188b5e8..b60472b7a6d 100644 --- a/packages/rs-drive-proof-verifier/src/verify.rs +++ b/packages/rs-drive-proof-verifier/src/verify.rs @@ -1,8 +1,8 @@ use dapi_grpc::platform::v0::{Proof, ResponseMetadata}; -use dpp::bls_signatures; +use dpp::bls; use crate::Error; -use dpp::bls_signatures::{Bls12381G2Impl, Pairing, Signature}; +use dpp::bls::Signature; use tenderdash_abci::{ proto::types::{CanonicalVote, SignedMsgType, StateId}, signatures::{Hashable, Signable}, @@ -134,8 +134,8 @@ pub(crate) fn verify_tenderdash_signature( } })?; - let pubkey = bls_signatures::PublicKey::::try_from(pubkey_bytes.as_slice()) - .map_err(|e| Error::InvalidPublicKey { + let pubkey = + bls::PublicKey::try_from(pubkey_bytes.as_slice()).map_err(|e| Error::InvalidPublicKey { error: e.to_string(), })?; @@ -164,20 +164,17 @@ pub(crate) fn verify_tenderdash_signature( pub fn verify_signature_digest( sign_digest: &[u8], signature: &[u8; 96], - public_key: &bls_signatures::PublicKey, + public_key: &bls::PublicKey, ) -> Result { if signature == &[0; 96] { return Err(Error::SignatureVerificationError { error: "empty signature".to_string(), }); } - let signature = Signature::Basic( - ::Signature::from_compressed(signature) - .into_option() - .ok_or(Error::SignatureVerificationError { - error: "Could not verify signature digest".to_string(), - })?, - ); + let signature = + Signature::from_compressed(signature).ok_or(Error::SignatureVerificationError { + error: "Could not verify signature digest".to_string(), + })?; Ok(signature.verify(public_key, sign_digest).is_ok()) } @@ -339,11 +336,13 @@ mod tests { } /// Helper: create a deterministic BLS key pair for testing. - fn test_keypair() -> ( - bls_signatures::SecretKey, - bls_signatures::PublicKey, - ) { - let sk = bls_signatures::SecretKey::::from_hash(b"test-key-seed"); + fn test_keypair() -> (bls::SecretKey, bls::PublicKey) { + let scalar = + hex::decode("659dc45452f29c17f96dda38ae1546e0e9e818423cdb8cc857f3728c518b4cb8") + .unwrap() + .try_into() + .unwrap(); + let sk = bls::SecretKey::from_be_bytes(&scalar).unwrap(); let pk = sk.public_key(); (sk, pk) } @@ -358,7 +357,7 @@ mod tests { impl ValidKeyContextProvider { fn new() -> Self { let (_, pk) = test_keypair(); - let pk_vec: Vec = (&pk).into(); + let pk_vec = pk.to_bytes().to_vec(); let mut pubkey_bytes = [0u8; 48]; pubkey_bytes.copy_from_slice(&pk_vec); Self { pubkey_bytes } diff --git a/packages/rs-platform-encryption/Cargo.toml b/packages/rs-platform-encryption/Cargo.toml index 7c6757d23a0..32c8f7a74ab 100644 --- a/packages/rs-platform-encryption/Cargo.toml +++ b/packages/rs-platform-encryption/Cargo.toml @@ -9,9 +9,9 @@ description = "Cryptographic utilities for Dash Platform (DIP-15 DashPay encrypt [dependencies] # Cryptography — direct deps only, so a consumer that needs just this crate # doesn't pull in/compile all of dashcore. `secp256k1` is pinned to the same -# 0.30 dashcore re-exports, so the public `SecretKey`/`PublicKey` types unify +# 0.33 dashcore re-exports, so the public `SecretKey`/`PublicKey` types unify # with dashcore-typed callers (platform-wallet, rs-sdk-ffi). -secp256k1 = { version = "0.30.0", features = ["std"] } +secp256k1 = { version = "0.33.1", features = ["std"] } aes = "0.8" cbc = "0.1" hmac = "0.12" @@ -21,4 +21,4 @@ thiserror = "1.0" [dev-dependencies] # Tests generate keypairs via secp256k1's RNG helpers (`generate_keypair`, # `secp256k1::rand`), gated behind the `rand` feature. -secp256k1 = { version = "0.30.0", features = ["std", "rand"] } +secp256k1 = { version = "0.33.1", features = ["std", "rand"] } diff --git a/packages/rs-platform-encryption/src/account_label.rs b/packages/rs-platform-encryption/src/account_label.rs index 3f5bd60067c..7d19e99f288 100644 --- a/packages/rs-platform-encryption/src/account_label.rs +++ b/packages/rs-platform-encryption/src/account_label.rs @@ -113,16 +113,15 @@ pub fn decrypt_account_label( mod tests { use super::*; use crate::ecdh::derive_shared_key_ecdh; + use secp256k1::generate_keypair; use secp256k1::rand::rngs::StdRng; use secp256k1::rand::{RngCore, SeedableRng}; - use secp256k1::Secp256k1; #[test] fn test_account_label_encryption() { let mut rng = StdRng::seed_from_u64(4); - let secp = Secp256k1::new(); - let (secret1, _public1) = secp.generate_keypair(&mut rng); - let (_secret2, public2) = secp.generate_keypair(&mut rng); + let (secret1, _public1) = generate_keypair(&mut rng); + let (_secret2, public2) = generate_keypair(&mut rng); // Derive shared key let shared_key = derive_shared_key_ecdh(&secret1, &public2); diff --git a/packages/rs-platform-encryption/src/compact_xpub.rs b/packages/rs-platform-encryption/src/compact_xpub.rs index 80101600948..b2b8ee6602a 100644 --- a/packages/rs-platform-encryption/src/compact_xpub.rs +++ b/packages/rs-platform-encryption/src/compact_xpub.rs @@ -139,16 +139,15 @@ pub fn parse_compact_xpub(bytes: &[u8]) -> Result { mod tests { use super::*; use crate::ecdh::derive_shared_key_ecdh; + use secp256k1::generate_keypair; use secp256k1::rand::rngs::StdRng; use secp256k1::rand::{RngCore, SeedableRng}; - use secp256k1::Secp256k1; #[test] fn test_extended_public_key_encryption() { let mut rng = StdRng::seed_from_u64(3); - let secp = Secp256k1::new(); - let (secret1, _public1) = secp.generate_keypair(&mut rng); - let (_secret2, public2) = secp.generate_keypair(&mut rng); + let (secret1, _public1) = generate_keypair(&mut rng); + let (_secret2, public2) = generate_keypair(&mut rng); // Derive shared key let shared_key = derive_shared_key_ecdh(&secret1, &public2); diff --git a/packages/rs-platform-encryption/src/ecdh.rs b/packages/rs-platform-encryption/src/ecdh.rs index 0b2ae3bc707..836978e03f8 100644 --- a/packages/rs-platform-encryption/src/ecdh.rs +++ b/packages/rs-platform-encryption/src/ecdh.rs @@ -21,25 +21,24 @@ pub fn derive_shared_key_ecdh(private_key: &SecretKey, public_key: &PublicKey) - let shared_secret = SharedSecret::new(public_key, private_key); let mut key = [0u8; 32]; - key.copy_from_slice(shared_secret.as_ref()); + key.copy_from_slice(shared_secret.as_secret_bytes()); key } #[cfg(test)] mod tests { use super::*; + use secp256k1::generate_keypair; use secp256k1::rand::rngs::StdRng; use secp256k1::rand::SeedableRng; - use secp256k1::Secp256k1; #[test] fn test_ecdh_key_derivation() { let mut rng = StdRng::seed_from_u64(1); - let secp = Secp256k1::new(); // Generate two key pairs - let (secret1, public1) = secp.generate_keypair(&mut rng); - let (secret2, public2) = secp.generate_keypair(&mut rng); + let (secret1, public1) = generate_keypair(&mut rng); + let (secret2, public2) = generate_keypair(&mut rng); // Derive shared keys from both sides let shared1 = derive_shared_key_ecdh(&secret1, &public2); @@ -57,14 +56,13 @@ mod tests { /// (b) the exact compressed-y-prefix-‖-x preimage convention. #[test] fn ecdh_matches_sha256_y_parity_prefix_convention() { - use secp256k1::{Scalar, Secp256k1}; + use secp256k1::Scalar; use sha2::{Digest, Sha256}; - let secp = Secp256k1::new(); - let priv_a = SecretKey::from_slice(&[0xC0u8; 32]).expect("valid scalar"); - let priv_b = SecretKey::from_slice(&[0x0Du8; 32]).expect("valid scalar"); - let pub_a = PublicKey::from_secret_key(&secp, &priv_a); - let pub_b = PublicKey::from_secret_key(&secp, &priv_b); + let priv_a = SecretKey::from_secret_bytes([0xC0u8; 32]).expect("valid scalar"); + let priv_b = SecretKey::from_secret_bytes([0x0Du8; 32]).expect("valid scalar"); + let pub_a = PublicKey::from_secret_key(&priv_a); + let pub_b = PublicKey::from_secret_key(&priv_b); let ab = derive_shared_key_ecdh(&priv_a, &pub_b); let ba = derive_shared_key_ecdh(&priv_b, &pub_a); @@ -75,7 +73,7 @@ mod tests { // SHA256( (0x02 | (P.y & 1)) ‖ P.x ). Pins that it's the compressed-y // prefix + x, NOT x‖y or some other layout. let scalar_a = Scalar::from_be_bytes([0xC0u8; 32]).expect("scalar in range"); - let shared_point = pub_b.mul_tweak(&secp, &scalar_a).expect("point mul"); + let shared_point = pub_b.mul_tweak(&scalar_a).expect("point mul"); let uncompressed = shared_point.serialize_uncompressed(); // 0x04 ‖ x(32) ‖ y(32) let prefix = 0x02u8 | (uncompressed[64] & 1); // y parity from the last y byte let mut preimage = Vec::with_capacity(33); diff --git a/packages/rs-platform-encryption/tests/fixtures/generate-historical-encryption.rs b/packages/rs-platform-encryption/tests/fixtures/generate-historical-encryption.rs new file mode 100644 index 00000000000..1945346fc4e --- /dev/null +++ b/packages/rs-platform-encryption/tests/fixtures/generate-historical-encryption.rs @@ -0,0 +1,44 @@ +//! Run as a platform-encryption example on Platform 6499c680c6bc311e8933f396423a79459e2a88bd. +//! `cargo run -p platform-encryption --example generate-historical-encryption --locked -- /path/to/output` +use platform_encryption::*; +use secp256k1::{PublicKey, Secp256k1, SecretKey}; +fn main() { + let secp = Secp256k1::new(); + let secret = SecretKey::from_slice(&[0xc0; 32]).unwrap(); + let recipient = SecretKey::from_slice(&[0x0d; 32]).unwrap(); + let public = PublicKey::from_secret_key(&secp, &recipient); + let shared = derive_shared_key_ecdh(&secret, &public); + let xpub = compact_xpub_bytes( + [0x11, 0x22, 0x33, 0x44], + [0xaa; 32], + PublicKey::from_secret_key(&secp, &SecretKey::from_slice(&[7; 32]).unwrap()).serialize(), + ); + let iv = [0x5a; 16]; + let out = std::path::PathBuf::from(std::env::args().nth(1).unwrap()); + for (name, value) in [ + ("shared-key", shared.to_vec()), + ("compact-xpub", xpub.to_vec()), + ( + "encrypted-xpub", + encrypt_extended_public_key(&shared, &iv, &xpub), + ), + ( + "encrypted-label", + encrypt_account_label(&shared, &iv, "My DashPay Account"), + ), + ( + "encrypted-empty-label", + encrypt_account_label(&shared, &iv, ""), + ), + ( + "encrypted-id", + encrypt_enc_to_user_id(&shared, &[0x23; 32]).to_vec(), + ), + ( + "encrypted-private-data", + encrypt_private_data(&shared, &iv, b"historical contact information"), + ), + ] { + std::fs::write(out.join(format!("{name}.bin")), value).unwrap(); + } +} diff --git a/packages/rs-platform-encryption/tests/historical_compatibility.rs b/packages/rs-platform-encryption/tests/historical_compatibility.rs new file mode 100644 index 00000000000..abf542f7415 --- /dev/null +++ b/packages/rs-platform-encryption/tests/historical_compatibility.rs @@ -0,0 +1,119 @@ +//! Frozen results from Platform 6499c680c6bc311e8933f396423a79459e2a88bd, +//! using secp256k1 0.30.0. These ciphertexts become signed DashPay document fields. +//! Reproduce with fixtures/generate-historical-encryption.rs in that checkout. + +use platform_encryption::*; +use secp256k1::{PublicKey, SecretKey}; + +const SHARED_KEY: [u8; 32] = [ + 0x41, 0x44, 0xa9, 0xee, 0xca, 0x1b, 0x8c, 0xd1, 0x88, 0x4c, 0x7f, 0xdb, 0x14, 0xa5, 0x6a, 0x23, + 0xa5, 0xe7, 0x63, 0x95, 0x4b, 0x21, 0x96, 0x6a, 0xa5, 0xa8, 0x62, 0xea, 0x27, 0x9d, 0x8d, 0x13, +]; + +const COMPACT_XPUB: [u8; 69] = [ + 0x11, 0x22, 0x33, 0x44, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0x02, 0x98, 0x9c, 0x0b, 0x76, 0xcb, 0x56, 0x39, 0x71, 0xfd, 0xc9, 0xbe, + 0xf3, 0x1e, 0xc0, 0x6c, 0x35, 0x60, 0xf3, 0x24, 0x9d, 0x6e, 0xe9, 0xe5, 0xd8, 0x3c, 0x57, 0x62, + 0x55, 0x96, 0xe0, 0x5f, 0x6f, +]; + +const ENCRYPTED_XPUB: [u8; 96] = [ + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x86, 0x4d, 0x1b, 0x38, 0x07, 0xcf, 0x80, 0xfd, 0x27, 0xdf, 0x6c, 0xac, 0x06, 0x3a, 0x51, 0x28, + 0xfd, 0x61, 0x19, 0xd0, 0xd4, 0x04, 0x91, 0xa7, 0x78, 0x8c, 0xb4, 0xe1, 0x97, 0x5b, 0xc4, 0x7e, + 0x50, 0x70, 0xc7, 0x91, 0x9e, 0x3d, 0x8c, 0x21, 0xab, 0x26, 0xbe, 0x1a, 0x76, 0x3e, 0x79, 0x08, + 0xb9, 0x7c, 0xd3, 0x2a, 0x5e, 0x36, 0x30, 0x9f, 0x3b, 0xf9, 0x53, 0x5c, 0x51, 0x9b, 0x1b, 0x32, + 0xb2, 0xf2, 0x06, 0x69, 0x6e, 0xc6, 0xd0, 0xe2, 0x44, 0xa2, 0xe1, 0x82, 0xfc, 0xea, 0xa7, 0x50, +]; + +const ENCRYPTED_LABEL: [u8; 48] = [ + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x75, 0x86, 0x80, 0xff, 0xe5, 0xeb, 0xc1, 0x97, 0xcf, 0x98, 0xb7, 0xea, 0x57, 0x52, 0x97, 0x29, + 0xce, 0xa6, 0xda, 0x4c, 0xc5, 0x69, 0x07, 0x9a, 0x85, 0xc1, 0xa7, 0xe7, 0xa8, 0x03, 0xe0, 0x62, +]; + +const ENCRYPTED_EMPTY_LABEL: [u8; 48] = [ + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0x8d, 0x5f, 0x41, 0x35, 0x9c, 0x02, 0x9e, 0x2f, 0x4b, 0xf2, 0xbe, 0x4e, 0x24, 0xd1, 0x48, 0x5f, + 0x66, 0x69, 0x25, 0x05, 0x94, 0xec, 0x49, 0x42, 0x2d, 0xee, 0xd5, 0x45, 0xac, 0x75, 0xe9, 0x6d, +]; + +const ENCRYPTED_ID: [u8; 32] = [ + 0xdb, 0x69, 0x2c, 0xa2, 0x2a, 0x6b, 0x23, 0xf0, 0x18, 0xa8, 0x39, 0xba, 0xcc, 0x39, 0x22, 0x8b, + 0xdb, 0x69, 0x2c, 0xa2, 0x2a, 0x6b, 0x23, 0xf0, 0x18, 0xa8, 0x39, 0xba, 0xcc, 0x39, 0x22, 0x8b, +]; + +const ENCRYPTED_PRIVATE_DATA: [u8; 48] = [ + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, + 0xb7, 0xac, 0x53, 0x1a, 0x78, 0xee, 0xdb, 0x9d, 0x80, 0xde, 0x82, 0x07, 0x49, 0x6d, 0xda, 0xbe, + 0xf7, 0x4a, 0xc3, 0xcb, 0x8b, 0x5e, 0xe5, 0x7c, 0x14, 0x1b, 0xe5, 0x44, 0xb0, 0x9e, 0x29, 0xe8, +]; + +#[test] +fn should_match_historical_ecdh_shared_secret() { + let sender = SecretKey::from_secret_bytes([0xc0; 32]).unwrap(); + let recipient = SecretKey::from_secret_bytes([0x0d; 32]).unwrap(); + assert_eq!( + derive_shared_key_ecdh(&sender, &PublicKey::from_secret_key(&recipient)), + SHARED_KEY + ); + assert_eq!( + derive_shared_key_ecdh(&recipient, &PublicKey::from_secret_key(&sender)), + SHARED_KEY + ); +} + +#[test] +fn should_match_historical_compact_xpub_and_ciphertext() { + let account = SecretKey::from_secret_bytes([7; 32]).unwrap(); + let xpub = compact_xpub_bytes( + [0x11, 0x22, 0x33, 0x44], + [0xaa; 32], + PublicKey::from_secret_key(&account).serialize(), + ); + assert_eq!(xpub, COMPACT_XPUB); + assert_eq!( + encrypt_extended_public_key(&SHARED_KEY, &[0x5a; 16], &xpub), + ENCRYPTED_XPUB + ); + assert_eq!( + decrypt_extended_public_key(&SHARED_KEY, &ENCRYPTED_XPUB).unwrap(), + COMPACT_XPUB + ); +} + +#[test] +fn should_match_historical_label_ciphertexts() { + for (label, expected) in [ + ("My DashPay Account", ENCRYPTED_LABEL.as_slice()), + ("", ENCRYPTED_EMPTY_LABEL.as_slice()), + ] { + assert_eq!( + encrypt_account_label(&SHARED_KEY, &[0x5a; 16], label), + expected + ); + assert_eq!(decrypt_account_label(&SHARED_KEY, expected).unwrap(), label); + } +} + +#[test] +fn should_match_historical_contact_info_ciphertexts() { + assert_eq!( + encrypt_enc_to_user_id(&SHARED_KEY, &[0x23; 32]), + ENCRYPTED_ID + ); + assert_eq!( + decrypt_enc_to_user_id(&SHARED_KEY, &ENCRYPTED_ID), + [0x23; 32] + ); + let plaintext = b"historical contact information"; + assert_eq!( + encrypt_private_data(&SHARED_KEY, &[0x5a; 16], plaintext), + ENCRYPTED_PRIVATE_DATA + ); + assert_eq!( + decrypt_private_data(&SHARED_KEY, &ENCRYPTED_PRIVATE_DATA).unwrap(), + plaintext + ); +} diff --git a/packages/rs-platform-wallet-ffi/ERROR_CODE_REGISTRY.md b/packages/rs-platform-wallet-ffi/ERROR_CODE_REGISTRY.md index 3399060f53b..0b68fb196c0 100644 --- a/packages/rs-platform-wallet-ffi/ERROR_CODE_REGISTRY.md +++ b/packages/rs-platform-wallet-ffi/ERROR_CODE_REGISTRY.md @@ -114,13 +114,14 @@ These are shipped ABI. Do not renumber. | 98 | `NotFound` | Sentinel — `Option` returned as an error | | 99 | `ErrorUnknown` | Sentinel — unmapped/flattened errors | -**Next allocatable integer: 59** — 27–58 are all claimed (27, 29, 31, 34–42 +**Next allocatable integer: 60** — 27–59 are all claimed (27, 29, 31, 34–42 and 46 merged; 43–45 proposed by active #4313 at head `0302b188ab`; 47 and 48 proposed by active #4356 (47 renumbered from 42, 48 from 43 — see their rows below); 49–54 proposed by active #4586 (the persister operation × kind block); 55–57 proposed by #4715 for pending identity-funded shield debits and durable recovery errors; 58 proposed by #4799 for an unavailable -identity balance response; 28, 30, +identity balance response; 59 proposed by #5322 for an SPV +teardown that needs a process restart; 28, 30, 32 and 33 reserved). **28, 30, 32 and 33 are RESERVED, not free**: 28 and 30 were vacated when the reservation trio moved to 34–36; 32 and 33 lapsed when their in-repo owners @@ -172,6 +173,7 @@ Fork-era numbers remain in the collision history, which is immutable record. | 56 | `ErrorShieldedRecoveryCorrupted` | #4715 | Proposed — durable shielded recovery data is malformed or invalid; preserved for diagnosis. Rust, Swift and Kotlin preserve this typed error | | 57 | `ErrorShieldedRecoveryKeysRequired` | #4715 | Proposed — recovery needs the account and compatible keys; ciphertext damage can produce the same symptom. Rust, Swift and Kotlin preserve this typed error | | 58 | `ErrorIdentityBalanceUnavailable` | #4799 | Proposed — Platform returned no balance for a managed identity. Retrying the read is safe; this is distinct from missing wallet ownership and must not trigger registration or funding | +| 59 | `ErrorSpvProcessRestartRequired` | #5322 | Proposed — SPV startup or teardown panicked; restart the host process. Not retryable, unlike 27, which SPV start, stop and storage clear now return while an SPV teardown is still running. Typed in Rust, Swift and Kotlin (the PR also types 27 on Kotlin) | **Code 31 left this table on 2026-08-04.** `ErrorSigningKeyUnavailable` sat here as #4183's proposal until #4183 merged (`189a3abb1c`); it is now in the merged diff --git a/packages/rs-platform-wallet-ffi/src/dashpay.rs b/packages/rs-platform-wallet-ffi/src/dashpay.rs index e1a321315b9..c731c6091ec 100644 --- a/packages/rs-platform-wallet-ffi/src/dashpay.rs +++ b/packages/rs-platform-wallet-ffi/src/dashpay.rs @@ -838,7 +838,7 @@ impl platform_wallet::ContactCryptoProvider for ResolverContactCryptoProvider { .map_err(|e| { platform_wallet::PlatformWalletError::InvalidIdentityData(e.to_string()) })?; - dashcore::secp256k1::SecretKey::from_slice(scalar.as_ref()) + dashcore::secp256k1::SecretKey::from_secret_bytes(*scalar) .map_err(|e| platform_wallet::PlatformWalletError::InvalidIdentityData(e.to_string())) } @@ -852,7 +852,7 @@ impl platform_wallet::ContactCryptoProvider for ResolverContactCryptoProvider { .map_err(|e| { platform_wallet::PlatformWalletError::InvalidIdentityData(e.to_string()) })?; - dashcore::secp256k1::SecretKey::from_slice(scalar.as_ref()) + dashcore::secp256k1::SecretKey::from_secret_bytes(*scalar) .map_err(|e| platform_wallet::PlatformWalletError::InvalidIdentityData(e.to_string())) } diff --git a/packages/rs-platform-wallet-ffi/src/derivation.rs b/packages/rs-platform-wallet-ffi/src/derivation.rs index 81b1020276d..5d30ac8ef6a 100644 --- a/packages/rs-platform-wallet-ffi/src/derivation.rs +++ b/packages/rs-platform-wallet-ffi/src/derivation.rs @@ -4,7 +4,6 @@ use std::ffi::CStr; use std::os::raw::c_char; use std::str::FromStr; -use dashcore::secp256k1::Secp256k1; use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; use key_wallet::mnemonic::Mnemonic; use zeroize::Zeroizing; @@ -90,16 +89,15 @@ pub unsafe extern "C" fn platform_wallet_derive_ext_priv_key_from_mnemonic( let master = unwrap_result_or_return!(ExtendedPrivKey::new_master(network, &*seed)); - let secp = Secp256k1::new(); - let derived = unwrap_result_or_return!(master.derive_priv(&secp, &path)); + let derived = unwrap_result_or_return!(master.derive_priv(&path)); - let secret = Zeroizing::new(derived.private_key.secret_bytes()); + let secret = Zeroizing::new(derived.private_key.to_secret_bytes()); std::ptr::copy_nonoverlapping(secret.as_ptr(), out_secret_key, 32); std::ptr::copy_nonoverlapping(derived.chain_code.as_ref().as_ptr(), out_chain_code, 32); if !out_public_key.is_null() { - let pubkey_bytes = derived.private_key.public_key(&secp).serialize(); + let pubkey_bytes = derived.private_key.public_key().serialize(); std::ptr::copy_nonoverlapping(pubkey_bytes.as_ptr(), out_public_key, 33); } diff --git a/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs b/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs index ed2463e3cdc..d2d9f0da1ec 100644 --- a/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs +++ b/packages/rs-platform-wallet-ffi/src/derive_identity_key_at_slot.rs @@ -81,8 +81,7 @@ unsafe fn derive_at_slot_inner( let pub_len = pub_box.len(); std::mem::forget(pub_box); - let secret_key = match dashcore::secp256k1::SecretKey::from_slice(derived.private_key.as_ref()) - { + let secret_key = match dashcore::secp256k1::SecretKey::from_secret_bytes(*derived.private_key) { Ok(k) => k, Err(e) => { drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut( diff --git a/packages/rs-platform-wallet-ffi/src/error.rs b/packages/rs-platform-wallet-ffi/src/error.rs index cf54ef43b03..4e13906dfe0 100644 --- a/packages/rs-platform-wallet-ffi/src/error.rs +++ b/packages/rs-platform-wallet-ffi/src/error.rs @@ -214,6 +214,8 @@ pub enum PlatformWalletFFIResultCode { /// in-flight sync pass was still running when a Clear / reset / /// sync-stop needed it provably drained. The operation failed closed /// (no state was wiped) and the host should retry once sync is idle. + /// Also returned by SPV start, stop and storage clear while an SPV + /// teardown is still running: call SPV stop again. /// NOT returned by `platform_wallet_manager_destroy` — with owned /// callback contexts (`release_fn`) a straggling worker keeps its /// context alive and releases it on exit, so destroy logs a non-clean @@ -602,6 +604,11 @@ pub enum PlatformWalletFFIResultCode { /// is safe; this does not imply missing ownership or require registration. ErrorIdentityBalanceUnavailable = 58, + /// Maps `PlatformWalletError::SpvProcessRestartRequired`: SPV startup or + /// teardown panicked. Not retryable, unlike + /// [`Self::ErrorShutdownIncomplete`]: restart the host process. + ErrorSpvProcessRestartRequired = 59, + /// The named thing does not exist. /// /// Originally (and still mostly) the code for every `Option` returned as an @@ -1019,6 +1026,9 @@ impl From for PlatformWalletFFIResult { PlatformWalletError::ShutdownIncomplete(..) => { PlatformWalletFFIResultCode::ErrorShutdownIncomplete } + PlatformWalletError::SpvProcessRestartRequired(..) => { + PlatformWalletFFIResultCode::ErrorSpvProcessRestartRequired + } // A signer failure can also reach this blanket impl wrapped as // `PlatformWalletError::Sdk(dash_sdk::Error::Protocol(..))` (any // wallet operation that propagates the SDK error via `?`). The @@ -2143,6 +2153,31 @@ mod tests { ); } + /// Hosts tell "stop again" (27) from "restart the process" (59) by code + /// alone, so neither SPV teardown error may flatten to `ErrorUnknown`. + #[test] + fn spv_teardown_errors_keep_distinct_pinned_codes() { + assert_eq!( + PlatformWalletFFIResultCode::ErrorSpvProcessRestartRequired as i32, + 59 + ); + + let restart: PlatformWalletFFIResult = + PlatformWalletError::SpvProcessRestartRequired("teardown task 1 panicked".into()) + .into(); + assert_eq!( + restart.code, + PlatformWalletFFIResultCode::ErrorSpvProcessRestartRequired + ); + + let retry: PlatformWalletFFIResult = + PlatformWalletError::ShutdownIncomplete("SPV teardown timed out".into()).into(); + assert_eq!( + retry.code, + PlatformWalletFFIResultCode::ErrorShutdownIncomplete + ); + } + #[test] fn shielded_insufficient_balance_code_is_pinned_at_41() { assert_eq!( diff --git a/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs b/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs index 9ce63c81f56..7beb37acde8 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_derive_and_persist.rs @@ -93,7 +93,6 @@ use std::ptr; use crate::types::{FFINetwork, Network}; use dashcore::hashes::Hash; -use dashcore::secp256k1::Secp256k1; use key_wallet::bip32::{ExtendedPrivKey, ExtendedPubKey}; use zeroize::{Zeroize, Zeroizing}; @@ -260,7 +259,6 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( let kw_network: Network = network.into(); let master = unwrap_result_or_return!(ExtendedPrivKey::new_master(kw_network, seed.as_ref())); - let secp = Secp256k1::new(); // ---- Walk derivation paths, persist, build pubkey-only rows -------------- let persister = &*persister_handle; @@ -298,7 +296,7 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( } }; - let derived = match master.derive_priv(&secp, &path) { + let derived = match master.derive_priv(&path) { Ok(d) => d, Err(e) => { cleanup(rows); @@ -313,13 +311,13 @@ pub unsafe extern "C" fn dash_sdk_derive_and_persist_identity_keys( }; // Materialize pubkey + hash160 once. - let extended_pub = ExtendedPubKey::from_priv(&secp, &derived); + let extended_pub = ExtendedPubKey::from_priv(&derived); let pub_bytes: [u8; 33] = extended_pub.public_key.serialize(); let pub_hash: [u8; 20] = dashcore::hashes::hash160::Hash::hash(&pub_bytes).to_byte_array(); // Hold the secret scalar in a buffer that drops with // `zeroize::Zeroize::zeroize` at scope end. - let mut priv_scalar: [u8; 32] = derived.private_key.secret_bytes(); + let mut priv_scalar: [u8; 32] = derived.private_key.to_secret_bytes(); let path_cstring = match CString::new(path.to_string()) { Ok(s) => s, diff --git a/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs b/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs index cecc569c051..837722a5dbd 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_key_preview.rs @@ -366,8 +366,8 @@ unsafe fn preview_identity_registration_keys_inner( // WIF: network-aware (mainnet → 0xCC, testnet/devnet/ // regtest → 0xEF) and compressed. Same construction // `key_wallet::derive_private_key_as_wif` performs. - let secret_key = dashcore::secp256k1::SecretKey::from_slice( - material.private_key.as_ref(), + let secret_key = dashcore::secp256k1::SecretKey::from_secret_bytes( + *material.private_key, ) .map_err(|e| { PlatformWalletFFIResult::err( @@ -455,7 +455,7 @@ unsafe fn preview_identity_registration_keys_inner( identity_index, key_index, )?; - let private_key = Zeroizing::new(ext_priv.private_key.secret_bytes()); + let private_key = Zeroizing::new(ext_priv.private_key.to_secret_bytes()); // Belt-and-braces: the pinned key-wallet rev zeroizes // `ExtendedPrivKey` on Drop; erase explicitly anyway // (cheap, and robust to an upstream Drop regression). diff --git a/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs b/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs index 144a8961aa8..fd4c6948d79 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_keys_from_mnemonic.rs @@ -2,7 +2,6 @@ use std::ffi::CString; -use dashcore::secp256k1::Secp256k1; use dashcore::PrivateKey as DashPrivateKey; use key_wallet::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey}; use key_wallet::dip9::{ @@ -319,7 +318,6 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( let kw_network: Network = network.into(); let master = unwrap_result_or_return!(ExtendedPrivKey::new_master(kw_network, seed.as_ref())); - let secp = Secp256k1::new(); let mut rows: Vec = Vec::with_capacity(key_count as usize); @@ -347,7 +345,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( } }; - let derived = match master.derive_priv(&secp, &path) { + let derived = match master.derive_priv(&path) { Ok(d) => d, Err(e) => { cleanup(rows); @@ -360,7 +358,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( ); } }; - let extended_pub = ExtendedPubKey::from_priv(&secp, &derived); + let extended_pub = ExtendedPubKey::from_priv(&derived); let public_key = extended_pub.public_key; let path_cstring = match CString::new(path.to_string()) { @@ -404,7 +402,7 @@ pub unsafe extern "C" fn dash_sdk_derive_identity_keys_from_mnemonic( public_key: pub_ptr, public_key_len: pub_len, private_key_wif: wif_cstring.into_raw(), - private_key_bytes: derived.private_key.secret_bytes(), + private_key_bytes: derived.private_key.to_secret_bytes(), }); } diff --git a/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs b/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs index 7dbda21ff29..e9cdf4bce92 100644 --- a/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs +++ b/packages/rs-platform-wallet-ffi/src/identity_registration_with_signer.rs @@ -704,7 +704,7 @@ pub unsafe extern "C" fn platform_wallet_derive_identity_keys_for_index( public_key: pub_ptr, public_key_len: pub_len, private_key_wif: wif_cstring.into_raw(), - private_key_bytes: ext_priv.private_key.secret_bytes(), + private_key_bytes: ext_priv.private_key.to_secret_bytes(), }); } Ok(rows) diff --git a/packages/rs-platform-wallet-ffi/src/invitation.rs b/packages/rs-platform-wallet-ffi/src/invitation.rs index bc258dd02ea..d73d5281a1d 100644 --- a/packages/rs-platform-wallet-ffi/src/invitation.rs +++ b/packages/rs-platform-wallet-ffi/src/invitation.rs @@ -850,7 +850,7 @@ mod tests { /// unknown wallet is looked up, and no signer/network work is attempted. #[test] fn claim_invitation_rejects_duplicate_key_ids_before_wallet_lookup() { - let voucher = SecretKey::from_slice(&[0x11u8; 32]).expect("valid scalar"); + let voucher = SecretKey::from_secret_bytes([0x11u8; 32]).expect("valid scalar"); let wif = PrivateKey::new(voucher, Network::Testnet).to_wif(); let uri = std::ffi::CString::new(format!("dashpay://invite?assetlocktx=aa&pk={wif}")).unwrap(); @@ -925,7 +925,7 @@ mod tests { /// (percent-decoded), and both are null for a link that carries neither. #[test] fn should_surface_inviter_display_name_and_avatar_in_the_preview() { - let key = SecretKey::from_slice(&[0x11u8; 32]).unwrap(); + let key = SecretKey::from_secret_bytes([0x11u8; 32]).unwrap(); let wif = PrivateKey::new(key, Network::Testnet).to_wif(); let txid = "ab".repeat(32); let take = |ptr: *mut c_char| -> Option { diff --git a/packages/rs-platform-wallet-ffi/src/manager.rs b/packages/rs-platform-wallet-ffi/src/manager.rs index 20f0c421b20..731fb5c6a32 100644 --- a/packages/rs-platform-wallet-ffi/src/manager.rs +++ b/packages/rs-platform-wallet-ffi/src/manager.rs @@ -711,9 +711,11 @@ pub unsafe extern "C" fn platform_wallet_manager_get_wallet( /// Destroy a PlatformWalletManager handle. /// -/// Runs the full lifecycle shutdown (bounded: quiesce + join every -/// coordinator, SPV, the payment-hook tasks, and the event adapter) and -/// removes the handle. Always returns `Success` for a live handle. +/// Runs the full lifecycle shutdown (quiesce + join every coordinator, +/// SPV, the payment-hook tasks, and the event adapter) and removes the +/// handle. Shutdown waits are bounded except for SPV's waits for a +/// concurrent start/stop and in-flight client queries. Always returns +/// `Success` for a live handle. /// /// A non-clean shutdown — a worker that outlived its join budget — is /// logged, **not** surfaced as an error, because it is no longer a diff --git a/packages/rs-platform-wallet-ffi/src/persistence.rs b/packages/rs-platform-wallet-ffi/src/persistence.rs index d95a1f31db8..9334cde2789 100644 --- a/packages/rs-platform-wallet-ffi/src/persistence.rs +++ b/packages/rs-platform-wallet-ffi/src/persistence.rs @@ -8968,7 +8968,6 @@ mod tests { use dashcore::blockdata::transaction::txout::TxOut; use dashcore::blockdata::transaction::Transaction; use dashcore::consensus::encode::serialize; - use dashcore::secp256k1::Secp256k1; use dashcore::{Network, ScriptBuf}; use key_wallet::account::{Account, AccountType, StandardAccountType}; use key_wallet::bip32::{ExtendedPrivKey, ExtendedPubKey}; @@ -9127,8 +9126,7 @@ mod tests { let seed = mnemonic.to_seed(""); let master = ExtendedPrivKey::new_master(Network::Testnet, &seed) .expect("master derivation must succeed"); - let secp = Secp256k1::new(); - let xpub = ExtendedPubKey::from_priv(&secp, &master); + let xpub = ExtendedPubKey::from_priv(&master); let account = Account::from_xpub( None, AccountType::Standard { @@ -9160,8 +9158,7 @@ mod tests { let seed = mnemonic.to_seed(""); let master = ExtendedPrivKey::new_master(Network::Testnet, &seed) .expect("master derivation must succeed"); - let secp = Secp256k1::new(); - let xpub = ExtendedPubKey::from_priv(&secp, &master); + let xpub = ExtendedPubKey::from_priv(&master); let account = Account::from_xpub(None, account_type, xpub, Network::Testnet) .expect("Account::from_xpub on a valid xpub must succeed"); let mut accounts = key_wallet::AccountCollection::new(); @@ -9275,8 +9272,7 @@ mod tests { let seed = mnemonic.to_seed(""); let master = ExtendedPrivKey::new_master(Network::Testnet, &seed) .expect("master derivation must succeed"); - let secp = Secp256k1::new(); - let xpub = ExtendedPubKey::from_priv(&secp, &master); + let xpub = ExtendedPubKey::from_priv(&master); let account = Account::from_xpub(None, AccountType::ProviderOwnerKeys, xpub, Network::Testnet) .expect("Account::from_xpub on a valid xpub must succeed"); diff --git a/packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs b/packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs index d25d32324d1..8564a771b3d 100644 --- a/packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs +++ b/packages/rs-platform-wallet-ffi/src/provider_key_at_index.rs @@ -31,6 +31,7 @@ //! too: the wallet-manager read guard is NEVER held across the Swift //! resolver callback. +use dashcore::eddsa::{EddsaPkBytes, EddsaPkHash}; use std::ffi::CString; use std::os::raw::c_char; @@ -76,7 +77,7 @@ pub struct ProviderKeyAtIndexFFI { /// on the empty state. pub legacy_public_key_hex: *mut c_char, /// Null-terminated lowercase hex of the 20-byte platform node id - /// (40 chars) — `hash160` of the Ed25519 public key. Null for + /// (40 chars) — `SHA256(ed25519 pubkey)[..20]` in canonical order. Null for /// operator keys (no node id) and on the empty state. pub node_id_hex: *mut c_char, /// Null-terminated lowercase hex of the raw 32-byte private scalar @@ -363,7 +364,7 @@ pub unsafe extern "C" fn platform_wallet_provider_key_at_index_free( /// Ed25519 public key. Pure helper — no wallet handle, no key material /// beyond the public key. /// -/// Wraps `dashcore::PlatformNodeId::from_ed25519_public_key` so the host +/// Converts through `EddsaPkHash` and returns canonical Tenderdash bytes so the host /// can render the node id of a persisted platform-node public key (which /// carries only the pubkey) without re-implementing the SHA-256 digest. /// @@ -385,7 +386,66 @@ pub unsafe extern "C" fn platform_wallet_platform_node_id_from_ed25519_pubkey( } let mut pk32 = [0u8; 32]; pk32.copy_from_slice(unsafe { std::slice::from_raw_parts(pubkey_ptr, 32) }); - let node_id = dashcore::PlatformNodeId::from_ed25519_public_key(&pk32).to_byte_array(); + let node_id = EddsaPkHash::from(EddsaPkBytes::from_bytes(pk32)).to_canonical_bytes(); unsafe { std::ptr::copy_nonoverlapping(node_id.as_ptr(), out_node_id_20, 20) }; true } + +#[cfg(test)] +mod tests { + use super::platform_wallet_platform_node_id_from_ed25519_pubkey; + + #[test] + fn should_return_canonical_platform_node_id_bytes() { + // RFC 8032 test 1 public key; expected SHA-256 prefix computed independently. + let public_key = + hex::decode("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a") + .expect("valid public key hex"); + let mut output = [0xa5; 22]; + assert!(unsafe { + platform_wallet_platform_node_id_from_ed25519_pubkey( + public_key.as_ptr(), + public_key.len(), + output[1..].as_mut_ptr(), + ) + }); + assert_eq!( + hex::encode(&output[1..21]), + "21fe31dfa154a261626bf854046fd2271b7bed4b" + ); + assert_eq!(output[0], 0xa5); + assert_eq!(output[21], 0xa5); + } + + #[test] + fn should_reject_invalid_node_id_inputs_without_writing() { + let public_key = [0u8; 33]; + for length in [0, 31, 33] { + let mut output = [0xa5; 20]; + assert!(!unsafe { + platform_wallet_platform_node_id_from_ed25519_pubkey( + public_key.as_ptr(), + length, + output.as_mut_ptr(), + ) + }); + assert_eq!(output, [0xa5; 20]); + } + let mut output = [0xa5; 20]; + assert!(!unsafe { + platform_wallet_platform_node_id_from_ed25519_pubkey( + std::ptr::null(), + 32, + output.as_mut_ptr(), + ) + }); + assert_eq!(output, [0xa5; 20]); + assert!(!unsafe { + platform_wallet_platform_node_id_from_ed25519_pubkey( + public_key.as_ptr(), + 32, + std::ptr::null_mut(), + ) + }); + } +} diff --git a/packages/rs-platform-wallet-ffi/src/secp256k1_primitives.rs b/packages/rs-platform-wallet-ffi/src/secp256k1_primitives.rs index d97d5e5f149..5615d7b2259 100644 --- a/packages/rs-platform-wallet-ffi/src/secp256k1_primitives.rs +++ b/packages/rs-platform-wallet-ffi/src/secp256k1_primitives.rs @@ -3,7 +3,7 @@ use std::slice; use dashcore::secp256k1::ecdh::shared_secret_point; -use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use dashcore::secp256k1::{PublicKey, SecretKey}; use zeroize::Zeroizing; use crate::error::*; @@ -34,14 +34,14 @@ fn parse_secret_key( ))); } - // `SecretKey::from_slice` converts the slice into an unguarded local - // `[u8; 32]` on its way to the returned key, so parsing straight from the - // caller's slice would leave a second, unscrubbed copy of the scalar on - // the stack. Staging it here keeps every copy this side owns wiped. + // Converting the caller's slice straight into the `[u8; 32]` + // `SecretKey::from_secret_bytes` takes would leave an unguarded copy of + // the scalar on the stack. Staging it here keeps every copy this side + // owns wiped. let mut staged = Zeroizing::new([0u8; 32]); staged.copy_from_slice(unsafe { slice::from_raw_parts(private_key, private_key_len) }); - SecretKey::from_byte_array(&staged) + SecretKey::from_secret_bytes(*staged) .map(WipingSecretKey) .map_err(|error| invalid_parameter(format!("Invalid secp256k1 private key: {error}"))) } @@ -94,7 +94,7 @@ pub unsafe extern "C" fn platform_wallet_secp256k1_compressed_public_key( check_ptr!(out_pubkey); let secret_key = unwrap_result_or_return!(parse_secret_key(seckey, seckey_len)); - let compressed = PublicKey::from_secret_key(&Secp256k1::new(), &secret_key.0).serialize(); + let compressed = PublicKey::from_secret_key(&secret_key.0).serialize(); std::ptr::copy_nonoverlapping(compressed.as_ptr(), out_pubkey, compressed.len()); PlatformWalletFFIResult::ok() } diff --git a/packages/rs-platform-wallet-ffi/src/sign_with_mnemonic_resolver.rs b/packages/rs-platform-wallet-ffi/src/sign_with_mnemonic_resolver.rs index 0225e653a77..c2ea005a996 100644 --- a/packages/rs-platform-wallet-ffi/src/sign_with_mnemonic_resolver.rs +++ b/packages/rs-platform-wallet-ffi/src/sign_with_mnemonic_resolver.rs @@ -41,7 +41,6 @@ use std::os::raw::c_char; use std::str::FromStr; use crate::types::{FFINetwork, Network}; -use dashcore::secp256k1::Secp256k1; use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; use zeroize::Zeroizing; @@ -274,12 +273,11 @@ pub unsafe extern "C" fn dash_sdk_sign_with_mnemonic_resolver_and_path( Ok(m) => m, Err(_) => return fail(SIGN_WITH_RESOLVER_ERR_DERIVATION), }; - let secp = Secp256k1::new(); - let derived = match master.derive_priv(&secp, &path) { + let derived = match master.derive_priv(&path) { Ok(d) => d, Err(_) => return fail(SIGN_WITH_RESOLVER_ERR_DERIVATION), }; - let secret_bytes: Zeroizing<[u8; 32]> = Zeroizing::new(derived.private_key.secret_bytes()); + let secret_bytes: Zeroizing<[u8; 32]> = Zeroizing::new(derived.private_key.to_secret_bytes()); // ---- Bind the derived key to the expected on-chain key ------------------- // Reject before signing if the key derived here doesn't reproduce the @@ -290,7 +288,7 @@ pub unsafe extern "C" fn dash_sdk_sign_with_mnemonic_resolver_and_path( // `validate_private_key_bytes` decision (33-byte expected = compressed // pubkey equality; 20-byte expected = `ripemd160_sha256` of it). if !expected_key_data.is_null() && expected_key_data_len > 0 { - let derived_pubkey = key_wallet::bip32::ExtendedPubKey::from_priv(&secp, &derived) + let derived_pubkey = key_wallet::bip32::ExtendedPubKey::from_priv(&derived) .public_key .serialize(); // Build the expected slice only for the two lengths the binding @@ -494,14 +492,11 @@ mod tests { // same mnemonic the resolver returns. let mnemonic = parse_mnemonic_any_language(ENGLISH_PHRASE).expect("mnemonic"); let seed = mnemonic.to_seed(""); - let secp = Secp256k1::new(); let master = ExtendedPrivKey::new_master(Network::Testnet, &seed).expect("master"); let derived = master - .derive_priv(&secp, &DerivationPath::from_str(path_str).unwrap()) + .derive_priv(&DerivationPath::from_str(path_str).unwrap()) .expect("derive"); - let expected_pubkey = ExtendedPubKey::from_priv(&secp, &derived) - .public_key - .serialize(); + let expected_pubkey = ExtendedPubKey::from_priv(&derived).public_key.serialize(); let mut sig_buf = [0u8; 128]; let mut sig_len: usize = 0; @@ -590,14 +585,11 @@ mod tests { let mnemonic = parse_mnemonic_any_language(ENGLISH_PHRASE).expect("mnemonic"); let seed = mnemonic.to_seed(""); - let secp = Secp256k1::new(); let master = ExtendedPrivKey::new_master(Network::Testnet, &seed).expect("master"); let derived = master - .derive_priv(&secp, &DerivationPath::from_str(path_str).unwrap()) + .derive_priv(&DerivationPath::from_str(path_str).unwrap()) .expect("derive"); - let pubkey = ExtendedPubKey::from_priv(&secp, &derived) - .public_key - .serialize(); + let pubkey = ExtendedPubKey::from_priv(&derived).public_key.serialize(); let expected_hash = dash_sdk::dpp::util::hash::ripemd160_sha256(&pubkey); let mut sig_buf = [0u8; 128]; diff --git a/packages/rs-platform-wallet-ffi/src/spv.rs b/packages/rs-platform-wallet-ffi/src/spv.rs index dc722ff47b7..33f6ad9f9dd 100644 --- a/packages/rs-platform-wallet-ffi/src/spv.rs +++ b/packages/rs-platform-wallet-ffi/src/spv.rs @@ -344,7 +344,8 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_connected_peers_free( let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(entries, count)); } -/// Whether the SPV client is currently running. +/// Whether SPV sync is running or starting. `false` once background sync has +/// failed. Never blocks, so an event callback may call it. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_spv_is_running( handle: Handle, @@ -352,7 +353,7 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_is_running( ) -> PlatformWalletFFIResult { check_ptr!(out_running); let option = - PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| manager.spv().is_started()); + PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| manager.spv().is_running()); *out_running = unwrap_option_or_return!(option); PlatformWalletFFIResult::ok() } @@ -579,6 +580,9 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_start( } /// Stop the SPV client. +/// +/// `ErrorShutdownIncomplete`: teardown is still running; call stop again. +/// `ErrorSpvProcessRestartRequired`: restart the host process. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_spv_stop( handle: Handle, @@ -586,10 +590,9 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_stop( // Under the registry guard on purpose — it mutates the manager's // runtime; see `platform_wallet_manager_spv_start`. // - // A stop that did not complete (the client stop ran over its budget, or - // the run loop survived the abort and was re-parked for a retry) is - // returned as an error: a start issued after it would find the parked - // run loop and spawn none. + // A stop that did not complete (teardown outlived the stop deadline and + // stays tracked for a retry, or it failed) is returned as an error: a + // start issued after it is refused until a later stop joins the teardown. let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(handle, |manager| { let spv = manager.spv_arc(); block_on_worker(async move { spv.stop().await }) @@ -651,6 +654,9 @@ pub unsafe extern "C" fn platform_wallet_manager_spv_rescan_filters( } /// Clear all persisted SPV storage (headers, filters, state). +/// +/// A running SPV client is stopped first and stays stopped; nothing is +/// cleared, and an error is returned, when that stop does not complete. #[no_mangle] pub unsafe extern "C" fn platform_wallet_manager_spv_clear_storage( handle: Handle, diff --git a/packages/rs-platform-wallet-ffi/src/utils.rs b/packages/rs-platform-wallet-ffi/src/utils.rs index bf84c88170e..770ae09777c 100644 --- a/packages/rs-platform-wallet-ffi/src/utils.rs +++ b/packages/rs-platform-wallet-ffi/src/utils.rs @@ -2,7 +2,7 @@ use crate::error::*; use crate::{check_ptr, unwrap_result_or_return}; use std::os::raw::{c_char, c_uchar}; -/// RAII guard that scrubs a `secp256k1::SecretKey`'s scalar on drop. `from_slice` +/// RAII guard that scrubs a `secp256k1::SecretKey`'s scalar on drop. `from_secret_bytes` /// allocates a 32-byte scalar copy of the caller's private key, and `SecretKey` /// has no `Drop` wipe of its own — so without this the copy would survive on the /// stack after the call returns. Mirrors `WipingSecretKey` in @@ -202,19 +202,21 @@ pub unsafe extern "C" fn platform_wallet_pubkey_hash_from_private_key( return -1; } use dashcore::hashes::Hash; - use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use dashcore::secp256k1::{PublicKey, SecretKey}; let sk_bytes = std::slice::from_raw_parts(private_key, 32); - let secp = Secp256k1::new(); - // `WipingSecretKey` scrubs the `from_slice`-allocated scalar copy on every + // `WipingSecretKey` scrubs the `from_secret_bytes`-allocated scalar copy on every // exit path (the success return below, the `Err` early return, and any // panic) — the caller's `private_key` bytes are theirs to manage, but this // copy must not linger. - let secret_key = match SecretKey::from_slice(sk_bytes) { + let secret_key = match <[u8; 32]>::try_from(sk_bytes) + .map_err(|_| dashcore::secp256k1::Error::InvalidSecretKey) + .and_then(SecretKey::from_secret_bytes) + { Ok(sk) => WipingSecretKey(sk), Err(_) => return -1, }; - let pubkey = PublicKey::from_secret_key(&secp, &secret_key.0).serialize(); + let pubkey = PublicKey::from_secret_key(&secret_key.0).serialize(); let hash = dashcore::hashes::hash160::Hash::hash(&pubkey); let h: [u8; 20] = hash.to_byte_array(); std::ptr::copy_nonoverlapping(h.as_ptr(), out_hash, 20); @@ -263,7 +265,7 @@ mod tests { #[test] fn test_pubkey_hash_from_private_key_matches_canonical_derivation() { use dashcore::hashes::Hash; - use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use dashcore::secp256k1::{PublicKey, SecretKey}; // A fixed, in-range scalar. let mut scalar = [0u8; 32]; @@ -275,9 +277,8 @@ mod tests { }; assert_eq!(rc, 0); - let secp = Secp256k1::new(); - let sk = SecretKey::from_slice(&scalar).expect("in-range scalar"); - let pubkey = PublicKey::from_secret_key(&secp, &sk).serialize(); + let sk = SecretKey::from_secret_bytes(scalar).expect("in-range scalar"); + let pubkey = PublicKey::from_secret_key(&sk).serialize(); let expected: [u8; 20] = dashcore::hashes::hash160::Hash::hash(&pubkey).to_byte_array(); assert_eq!(out, expected); } diff --git a/packages/rs-platform-wallet-storage/Cargo.toml b/packages/rs-platform-wallet-storage/Cargo.toml index fe491fcc76b..69f3ca9aefc 100644 --- a/packages/rs-platform-wallet-storage/Cargo.toml +++ b/packages/rs-platform-wallet-storage/Cargo.toml @@ -90,7 +90,7 @@ schemars = { version = "1", optional = true, default-features = false } # crate itself is sample/CLI). Verified to build under MSRV 1.92. argon2 = { version = "=0.5.3", optional = true } chacha20poly1305 = { version = "=0.10.1", optional = true } -zeroize = { version = "=1.8.2", features = ["derive"], optional = true } +zeroize = { version = "=1.9.0", features = ["derive"], optional = true } subtle = { version = "=2.6.1", optional = true } # CSPRNG for AEAD nonces and the vault salt; exact-pinned like the rest # of the crypto stack so the random source is not the loose one. @@ -183,7 +183,7 @@ filetime = "0.2" # sqlite,cli`) is a local/manual check, NOT a CI gate — no workflow runs # it today. Keep this dev-dep regardless: it is what lets the off-state # build succeed when someone does run it. -zeroize = { version = "=1.8.2", features = ["derive"] } +zeroize = { version = "=1.9.0", features = ["derive"] } tracing-test = { version = "0.2", features = ["no-env-filter"] } serial_test = "3" # `default-features = false` so the off-state build diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index d3da739bcba..5c6fe75d5d1 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -92,6 +92,7 @@ name = "shielded_chunk_timing_bench" required-features = ["shielded"] [dev-dependencies] +tempfile = "3.27.0" # In-process Signer for the manual testnet # verification example (`examples/dpns_marketplace_testnet.rs`). simple-signer = { path = "../simple-signer", features = ["state-transitions"] } diff --git a/packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs b/packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs index fcc44cdf40a..535e6453980 100644 --- a/packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs +++ b/packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs @@ -115,11 +115,10 @@ fn dapi_addresses() -> AddressList { /// Whether `sk_bytes` is the private key for `key` (33-byte pubkey for /// ECDSA_SECP256K1, hash160 for ECDSA_HASH160). fn private_key_matches(key: &dpp::identity::IdentityPublicKey, sk_bytes: &[u8; 32]) -> bool { - let secp = dashcore::secp256k1::Secp256k1::new(); - let Ok(sk) = dashcore::secp256k1::SecretKey::from_byte_array(sk_bytes) else { + let Ok(sk) = dashcore::secp256k1::SecretKey::from_secret_bytes(*sk_bytes) else { return false; }; - let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&secp, &sk).serialize(); + let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&sk).serialize(); match key.key_type() { KeyType::ECDSA_SECP256K1 => key.data().as_slice() == pubkey.as_slice(), KeyType::ECDSA_HASH160 => { @@ -174,7 +173,7 @@ fn parse_private_key(raw: &str) -> Option<[u8; 32]> { } dashcore::PrivateKey::from_wif(trimmed) .ok() - .map(|pk| pk.inner.secret_bytes()) + .map(|pk| pk.inner.to_secret_bytes()) } async fn discover( diff --git a/packages/rs-platform-wallet/src/error.rs b/packages/rs-platform-wallet/src/error.rs index 66c61cfa864..8ba719674d0 100644 --- a/packages/rs-platform-wallet/src/error.rs +++ b/packages/rs-platform-wallet/src/error.rs @@ -812,6 +812,15 @@ pub enum PlatformWalletError { #[error("SPV error: {0}")] SpvError(String), + /// SPV startup or teardown panicked and may have left background work + /// running. Not retryable: restart the process. + /// FFI mirror: `PlatformWalletFFIResultCode::ErrorSpvProcessRestartRequired`. + #[error( + "SPV startup or teardown panicked and may have left background work \ + running; restart the process before using SPV again: {0}" + )] + SpvProcessRestartRequired(String), + #[error("Token operation failed: {0}")] TokenError(String), @@ -952,6 +961,8 @@ pub enum PlatformWalletError { /// fail-closed. The wedged pass may still fire persistence / event /// callbacks; the host must keep its callback context alive and must /// not commit any wipe it was about to pair with this call. + /// Also returned by SPV start, stop and storage clear while an SPV + /// teardown is still running: stop SPV again. /// FFI mirror: `PlatformWalletFFIResultCode::ErrorShutdownIncomplete`. #[error("Background sync did not quiesce: {0}")] ShutdownIncomplete(String), diff --git a/packages/rs-platform-wallet/src/manager/accessors.rs b/packages/rs-platform-wallet/src/manager/accessors.rs index 1f9ed240c84..c4ff3dfb6b2 100644 --- a/packages/rs-platform-wallet/src/manager/accessors.rs +++ b/packages/rs-platform-wallet/src/manager/accessors.rs @@ -1,5 +1,6 @@ //! Read-only accessors on [`PlatformWalletManager`]. +use dashcore::eddsa::{EddsaPkBytes, EddsaPkHash}; use std::sync::Arc; use dashcore::{OutPoint, Txid}; @@ -1288,9 +1289,8 @@ impl PlatformWalletManager

{ for entry in pool.addresses.values() { if let Some(PublicKeyType::EdDSA(pk)) = &entry.public_key { if let Ok(pk32) = <[u8; 32]>::try_from(pk.as_slice()) { - let node_id = - dashcore::PlatformNodeId::from_ed25519_public_key(&pk32) - .to_byte_array(); + let node_id = EddsaPkHash::from(EddsaPkBytes::from_bytes(pk32)) + .to_canonical_bytes(); platform_index.insert(node_id, entry.index); } } diff --git a/packages/rs-platform-wallet/src/manager/mod.rs b/packages/rs-platform-wallet/src/manager/mod.rs index 51a57c32451..a442f8a48c4 100644 --- a/packages/rs-platform-wallet/src/manager/mod.rs +++ b/packages/rs-platform-wallet/src/manager/mod.rs @@ -61,7 +61,7 @@ pub enum WalletWorker { ShieldedSync, /// SPV runtime — the network event source feeding every persister- /// visible wallet event. Not a registry worker: `SpvRuntime::stop` - /// owns its (bounded, abort-escalating) join, and + /// owns its teardown task and bounds only the wait for it, and /// [`shutdown`](PlatformWalletManager::shutdown) folds the stop /// outcome into the report so a failed SPV stop can never hide /// behind a clean coordinator join. @@ -908,14 +908,18 @@ impl PlatformWalletManager

{ /// 4. The event adapter — the sink those stores feed into — drains /// LAST. /// - /// **Every phase is bounded.** SPV stop owns its own abort-escalating - /// join; the payment-hook drain is bounded by `PAYMENT_DRAIN_BUDGET`; - /// the coordinator drains run concurrently under - /// `COORDINATOR_DRAIN_BUDGET`; the registry join uses each worker's - /// join budget; the adapter join is bounded too (its live handle is - /// re-parked on timeout so a retry re-joins it). A wedged await - /// therefore surfaces as a non-clean report instead of hanging the - /// FFI's `destroy` forever. + /// **Every phase but SPV stop's lock waits is bounded.** SPV stop + /// bounds its wait for the teardown task, which it never aborts: a + /// teardown still running at the deadline is reported non-clean and + /// stays owned for a later stop to join. Before that deadline starts, + /// SPV stop waits without bound for a concurrent start or stop and + /// for client queries already in flight. The payment-hook drain is + /// bounded by `PAYMENT_DRAIN_BUDGET`; the coordinator drains run + /// concurrently under `COORDINATOR_DRAIN_BUDGET`; the registry join + /// uses each worker's join budget; the adapter join is bounded too + /// (its live handle is re-parked on timeout so a retry re-joins it). + /// Past those SPV lock waits, a wedged await therefore surfaces as a + /// non-clean report instead of hanging the FFI's `destroy` forever. /// /// Returns a [`ShutdownReport`] keyed by [`WalletWorker`] — including /// the non-registry workers [`WalletWorker::Spv`], @@ -927,10 +931,10 @@ impl PlatformWalletManager

{ /// /// [`WorkerStatus`]: dash_async::WorkerStatus pub async fn shutdown(&self) -> ShutdownReport { - // SPV first: it is the event source feeding everything below, and - // its `stop` owns a bounded, abort-escalating join of the run-loop - // task. Its outcome lands in the report — a failed stop must not - // hide behind a clean coordinator join. + // SPV first: it is the event source feeding everything below. Its + // `stop` bounds the wait for teardown but never aborts it, so a + // timeout means SPV work is still live. Its outcome lands in the + // report — a failed stop must not hide behind a clean coordinator join. let spv_status = match self.spv_manager.stop().await { Ok(()) => WorkerStatus::Ok, Err(error) => { diff --git a/packages/rs-platform-wallet/src/masternode/list.rs b/packages/rs-platform-wallet/src/masternode/list.rs index 41614c029a5..0cc701b8311 100644 --- a/packages/rs-platform-wallet/src/masternode/list.rs +++ b/packages/rs-platform-wallet/src/masternode/list.rs @@ -227,8 +227,10 @@ mod tests { }, }; let list = MasternodeList::build( - [(pro_tx, entry.into())].into_iter().collect(), - Default::default(), + [(pro_tx, std::sync::Arc::new(entry.into()))] + .into_iter() + .collect::(), + std::collections::BTreeMap::new(), BlockHash::from_byte_array([0u8; 32]), 0, ) diff --git a/packages/rs-platform-wallet/src/masternode/locator.rs b/packages/rs-platform-wallet/src/masternode/locator.rs index faf0513f8f8..25083796d78 100644 --- a/packages/rs-platform-wallet/src/masternode/locator.rs +++ b/packages/rs-platform-wallet/src/masternode/locator.rs @@ -26,19 +26,20 @@ //! `Unverifiable` when the reference (owner key hash, payout hash) isn't known //! yet — never a false pass. +use dashcore::base64::engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}; +use dashcore::base64::{alphabet, Engine}; +use dashcore::eddsa::{EddsaPkBytes, EddsaPkHash}; use std::collections::{BTreeSet, HashMap}; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use dash_sdk::platform::types::identity::NonUniquePublicKeyHashQuery; use dash_sdk::platform::Fetch; -use dashcore::blsful::{ - Bls12381G2Impl, PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, SerializationFormat, -}; -use dashcore::ed25519_dalek::SigningKey; use dashcore::hashes::{hash160, Hash}; -use dashcore::secp256k1::{PublicKey as SecpPublicKey, Secp256k1, SecretKey as SecpSecretKey}; -use dashcore::{Network, PlatformNodeId, PrivateKey}; +use dashcore::secp256k1::{PublicKey as SecpPublicKey, SecretKey as SecpSecretKey}; +use dashcore::{Network, PrivateKey}; +use dpp::bls::SecretKey as BlsSecretKey; +use dpp::ed25519_dalek::SigningKey; use dpp::identifier::MasternodeIdentifiers; use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; @@ -223,7 +224,7 @@ fn is_mainnet(network: Network) -> bool { /// only when below the group order; ed25519 accepts any 32 bytes as a seed. fn secret_candidates(bytes: &[u8; 32]) -> Vec { let mut out = Vec::with_capacity(3); - if SecpSecretKey::from_slice(bytes).is_ok() { + if SecpSecretKey::from_secret_bytes(*bytes).is_ok() { out.push(MasternodeLocatorInput::Secret(LocatorSecret::Ecdsa { secret: Zeroizing::new(*bytes), compressed: true, @@ -314,7 +315,7 @@ pub fn parse_locator_input( } return Ok(ParsedLocatorInput { candidates: vec![MasternodeLocatorInput::Secret(LocatorSecret::Ecdsa { - secret: Zeroizing::new(key.inner.secret_bytes()), + secret: Zeroizing::new(key.inner.to_secret_bytes()), compressed: key.compressed, })], }); @@ -322,7 +323,11 @@ pub fn parse_locator_input( // base64: dashmate's 64-byte node key, or a bare 32-byte secret. { - if let Ok(bytes) = dashcore::base64::decode(trimmed) { + let decoder = GeneralPurpose::new( + &alphabet::STANDARD, + GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent), + ); + if let Ok(bytes) = decoder.decode(trimmed) { match bytes.len() { 64 => { let seed = ed25519_seed_from_node_key(&bytes)?; @@ -353,8 +358,8 @@ pub fn parse_locator_input( /// hash160 of the secp256k1 public key for `secret`, or `None` when the /// scalar is out of range. pub fn ecdsa_key_id(secret: &[u8; 32], compressed: bool) -> Option<[u8; 20]> { - let sk = SecpSecretKey::from_slice(secret).ok()?; - let pk = SecpPublicKey::from_secret_key(&Secp256k1::signing_only(), &sk); + let sk = SecpSecretKey::from_secret_bytes(*secret).ok()?; + let pk = SecpPublicKey::from_secret_key(&sk); let bytes: Vec = if compressed { pk.serialize().to_vec() } else { @@ -364,24 +369,16 @@ pub fn ecdsa_key_id(secret: &[u8; 32], compressed: bool) -> Option<[u8; 20]> { } /// `(basic, legacy)` 48-byte serializations of the BLS public key for -/// `secret`, or `None` when the scalar is not below the group order. +/// `secret`, or `None` when the scalar reduces to zero. pub fn bls_public_keys(secret: &[u8; 32]) -> Option<([u8; 48], [u8; 48])> { - let sk: BlsSecretKey = - Option::from(BlsSecretKey::::from_be_bytes(secret))?; - let pk = BlsPublicKey::from(&sk); - let basic: [u8; 48] = pk.to_bytes().as_slice().try_into().ok()?; - let legacy: [u8; 48] = pk - .to_bytes_with_mode(SerializationFormat::Legacy) - .as_slice() - .try_into() - .ok()?; - Some((basic, legacy)) + let pk = BlsSecretKey::from_be_bytes(secret)?.public_key(); + Some((pk.to_bytes(), pk.to_legacy_bytes().ok()?)) } /// Tenderdash node id for an ed25519 `seed`. pub fn ed25519_node_id(seed: &[u8; 32]) -> [u8; 20] { let public = SigningKey::from_bytes(seed).verifying_key().to_bytes(); - PlatformNodeId::from_ed25519_public_key(&public).to_byte_array() + EddsaPkHash::from(EddsaPkBytes::from_bytes(public)).to_canonical_bytes() } // --------------------------------------------------------------------------- @@ -1100,13 +1097,33 @@ mod tests { assert!(parse_locator_input(&wif(Network::Testnet), Network::Devnet).is_ok()); } + #[test] + fn should_accept_unpadded_base64_secrets_and_node_keys() { + let seed = [0x42u8; 32]; + let mut node_key = seed.to_vec(); + node_key.extend_from_slice(&SigningKey::from_bytes(&seed).verifying_key().to_bytes()); + for bytes in [seed.as_slice(), node_key.as_slice()] { + let unpadded = dashcore::base64::engine::general_purpose::STANDARD_NO_PAD.encode(bytes); + let padded = dashcore::base64::engine::general_purpose::STANDARD.encode(bytes); + let parsed = parse_locator_input(&unpadded, Network::Mainnet).expect("unpadded key"); + assert_eq!( + parsed.candidates.len(), + parse_locator_input(&padded, Network::Mainnet) + .unwrap() + .candidates + .len() + ); + assert!(parsed.candidates.iter().any(|candidate| matches!(candidate, MasternodeLocatorInput::Secret(LocatorSecret::Ed25519(s)) if **s == seed))); + } + } + #[test] fn dashmate_node_key_parses_when_consistent() { let seed = [0x42u8; 32]; let public = SigningKey::from_bytes(&seed).verifying_key().to_bytes(); let mut node_key = seed.to_vec(); node_key.extend_from_slice(&public); - let b64 = dashcore::base64::encode(&node_key); + let b64 = dashcore::base64::engine::general_purpose::STANDARD.encode(&node_key); let parsed = parse_locator_input(&b64, Network::Mainnet).unwrap(); assert_eq!(parsed.candidates.len(), 1); @@ -1124,8 +1141,11 @@ mod tests { // A corrupted public half is rejected, not silently accepted. node_key[40] ^= 0x01; assert_eq!( - parse_locator_input(&dashcore::base64::encode(&node_key), Network::Mainnet) - .unwrap_err(), + parse_locator_input( + &dashcore::base64::engine::general_purpose::STANDARD.encode(&node_key), + Network::Mainnet + ) + .unwrap_err(), LocatorParseError::NodeKeyMismatch ); } @@ -1135,10 +1155,7 @@ mod tests { #[test] fn ecdsa_key_id_matches_dashcore_address_hash() { let key = PrivateKey::from_byte_array(&SECP_SECRET, Network::Mainnet).unwrap(); - let expected: [u8; 20] = key - .public_key(&Secp256k1::new()) - .pubkey_hash() - .to_byte_array(); + let expected: [u8; 20] = key.public_key().pubkey_hash().to_byte_array(); assert_eq!(secp_key_id(), expected); assert_ne!( ecdsa_key_id(&SECP_SECRET, false).unwrap(), diff --git a/packages/rs-platform-wallet/src/masternode/record.rs b/packages/rs-platform-wallet/src/masternode/record.rs index ef49abb979d..71589eca833 100644 --- a/packages/rs-platform-wallet/src/masternode/record.rs +++ b/packages/rs-platform-wallet/src/masternode/record.rs @@ -347,15 +347,7 @@ where agg.operator_height = height; } if agg.platform_node_id.is_none() || height >= agg.platform_node_height { - // Evonode-only; `None` on a regular masternode. - // `platform_node_id` is a `PlatformNodeId` newtype - // (rust-dashcore #885) whose `consensus_decode` normalizes - // the wire's reversed uint160-internal bytes to the - // canonical Tenderdash `SHA256(pubkey)[..20]` order - // (rust-dashcore #887/#889), so `to_byte_array()` here is - // already canonical and matches the derived ownership - // index (`accessors.rs`) and dashmate display directly — - // do NOT reverse platform-side. + // Evonode IDs use canonical Tenderdash bytes in the ownership index. if let Some(node_id) = p.platform_node_id { agg.platform_node_id = Some(node_id.to_byte_array()); agg.platform_node_height = height; @@ -372,9 +364,6 @@ where agg.platform_http_port = p.platform_http_port; agg.service_height = height; } - // ProUpServ's `platform_node_id` is now `Option` - // (rust-dashcore #885, was `Option<[u8; 20]>`); decoded bytes - // are canonical forward order (see the ProRegTx arm above). if let Some(node_id) = p.platform_node_id { if agg.platform_node_id.is_none() || height >= agg.platform_node_height { agg.platform_node_id = Some(node_id.to_byte_array()); diff --git a/packages/rs-platform-wallet/src/masternode/update_service.rs b/packages/rs-platform-wallet/src/masternode/update_service.rs index 0d12b781ce5..7e6f1719d34 100644 --- a/packages/rs-platform-wallet/src/masternode/update_service.rs +++ b/packages/rs-platform-wallet/src/masternode/update_service.rs @@ -22,11 +22,11 @@ use dashcore::blockdata::transaction::special_transaction::{ SpecialTransactionBasePayloadEncodable, TransactionPayload, }; use dashcore::bls_sig_utils::BLSSignature; -use dashcore::blsful::{Bls12381G2Impl, SecretKey as BlsSecretKey, SignatureSchemes}; use dashcore::hash_types::InputsHash; use dashcore::hashes::Hash; use dashcore::platform_node_id::PlatformNodeId; use dashcore::{Address as DashAddress, Network, Transaction, Txid}; +use dpp::bls::SecretKey as BlsSecretKey; use key_wallet::wallet::managed_wallet_info::transaction_builder::{ BuilderError, TransactionBuilder, TransactionSigner, }; @@ -405,25 +405,13 @@ pub(crate) fn finalize_update_service_payload( let mut finalized = placeholder.clone(); finalized.inputs_hash = unsigned.hash_inputs(); - let secret = Option::>::from( - BlsSecretKey::::from_be_bytes(operator_secret), - ) - .ok_or_else(|| { + let secret = BlsSecretKey::from_be_bytes(operator_secret).ok_or_else(|| { BuilderError::SigningFailed("the operator key is not a valid BLS secret".into()) })?; let signature = secret - .sign( - SignatureSchemes::Basic, - finalized.base_payload_hash().as_byte_array(), - ) + .sign(finalized.base_payload_hash().as_byte_array()) .map_err(|e| BuilderError::SigningFailed(format!("BLS payload signing failed: {e}")))?; - let signature_bytes: [u8; 96] = signature - .to_bytes_with_mode(dashcore::blsful::SerializationFormat::Modern) - .as_slice() - .try_into() - .map_err(|_| { - BuilderError::SigningFailed("BLS signature did not serialize to 96 bytes".into()) - })?; + let signature_bytes = signature.to_bytes(); finalized.payload_sig = BLSSignature::from(signature_bytes); Ok(TransactionPayload::ProviderUpdateServicePayloadType( finalized, @@ -465,7 +453,7 @@ mod tests { use crate::test_support::{ funded_wallet_manager, funded_wallet_manager_with_outputs, WalletSigner, }; - use dashcore::blsful::{PublicKey as BlsPublicKey, Signature as BlsSignature}; + use dpp::bls::{PublicKey as BlsPublicKey, Signature as BlsSignature}; use key_wallet::account::StandardAccountType; use std::sync::{Arc, Mutex}; @@ -799,14 +787,9 @@ mod tests { // The payload signature verifies under the basic scheme against the // operator public key, over base_payload_hash — the exact convention // `verify_message_digest` checks real mainnet signatures with. - let secret = Option::>::from( - BlsSecretKey::::from_be_bytes(&OPERATOR_SECRET), - ) - .expect("valid test scalar"); + let secret = BlsSecretKey::from_be_bytes(&OPERATOR_SECRET).expect("valid test scalar"); let public_key = BlsPublicKey::from(&secret); - let signature: BlsSignature = payload - .payload_sig - .try_into() + let signature = BlsSignature::from_compressed(payload.payload_sig.as_bytes()) .expect("compressed signature decodes"); signature .verify(&public_key, payload.base_payload_hash().as_byte_array()) diff --git a/packages/rs-platform-wallet/src/spv/peers.rs b/packages/rs-platform-wallet/src/spv/peers.rs index 1fd93547850..6c179b5d58b 100644 --- a/packages/rs-platform-wallet/src/spv/peers.rs +++ b/packages/rs-platform-wallet/src/spv/peers.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use std::net::SocketAddr; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard, PoisonError}; use dash_spv::network::NetworkEvent; use dash_spv::EventHandler; @@ -49,28 +49,30 @@ pub(crate) struct PeerTracker { } impl PeerTracker { - /// Addresses from the latest `PeersUpdated` event. - pub(crate) fn snapshot(&self) -> Vec { + /// Lock the snapshot, recovering from poisoning: it is only cloned, + /// cleared or replaced whole, so a panicking holder cannot tear it. + fn connected(&self) -> MutexGuard<'_, Vec> { self.connected .lock() - .expect("peer tracker mutex poisoned") - .clone() + .unwrap_or_else(PoisonError::into_inner) + } + + /// Addresses from the latest `PeersUpdated` event. + pub(crate) fn snapshot(&self) -> Vec { + self.connected().clone() } /// Forget all peers. Called when the SPV client stops so a stale /// snapshot can't outlive the connections it describes. pub(crate) fn clear(&self) { - self.connected - .lock() - .expect("peer tracker mutex poisoned") - .clear(); + self.connected().clear(); } } impl EventHandler for PeerTracker { fn on_network_event(&self, event: &NetworkEvent) { if let NetworkEvent::PeersUpdated { addresses, .. } = event { - *self.connected.lock().expect("peer tracker mutex poisoned") = addresses.clone(); + *self.connected() = addresses.clone(); } } } @@ -139,7 +141,7 @@ mod tests { } fn masternode_list(entries: Vec<(SocketAddr, EntryMasternodeType)>) -> MasternodeList { - let masternodes = entries + let masternodes: dashcore::sml::masternode_list::MasternodeMap = entries .into_iter() .enumerate() .map(|(i, (service, mn_type))| { @@ -156,12 +158,12 @@ mod tests { is_valid: true, mn_type, }; - (pro_tx_hash, entry.into()) + (pro_tx_hash, std::sync::Arc::new(entry.into())) }) .collect(); MasternodeList::build( masternodes, - Default::default(), + std::collections::BTreeMap::new(), BlockHash::from_byte_array([0u8; 32]), 0, ) @@ -232,4 +234,28 @@ mod tests { tracker.clear(); assert!(tracker.snapshot().is_empty()); } + + /// The tracker runs inside dash-spv's event dispatch, so a poisoned + /// mutex must not turn every later peer event into a panic there. + #[test] + fn should_keep_tracking_peers_after_the_mutex_is_poisoned() { + let tracker = std::sync::Arc::new(PeerTracker::default()); + let poisoner = std::sync::Arc::clone(&tracker); + let _ = std::thread::spawn(move || { + let _guard = poisoner.connected.lock().unwrap(); + panic!("mock panic while holding the peer tracker mutex"); + }) + .join(); + assert!(tracker.connected.is_poisoned()); + + tracker.on_network_event(&NetworkEvent::PeersUpdated { + connected_count: 1, + addresses: vec![socket([1, 1, 1, 1], 9999)], + best_height: Some(100), + }); + assert_eq!(tracker.snapshot(), vec![socket([1, 1, 1, 1], 9999)]); + + tracker.clear(); + assert!(tracker.snapshot().is_empty()); + } } diff --git a/packages/rs-platform-wallet/src/spv/runtime.rs b/packages/rs-platform-wallet/src/spv/runtime.rs index d5090e32686..beb8bd157ce 100644 --- a/packages/rs-platform-wallet/src/spv/runtime.rs +++ b/packages/rs-platform-wallet/src/spv/runtime.rs @@ -1,11 +1,13 @@ //! SPV client runtime — manages the DashSpvClient lifecycle. -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; +use std::future::Future; +use std::pin::pin; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use std::task::{Context, Poll, Waker}; use std::time::Duration; use tokio::sync::RwLock; -use tokio::task::JoinHandle; +use tokio::task::{JoinError, JoinHandle}; use dashcore::sml::llmq_type::LLMQType; use dashcore::sml::masternode_list::MasternodeList; @@ -28,117 +30,49 @@ use crate::wallet::platform_wallet::PlatformWalletInfo; type SpvClient = DashSpvClient, PeerNetworkManager, DiskStorageManager>; -/// Graceful join budget for the SPV run loop before escalating to `abort`. +/// Maximum wait per stop call; the owned teardown continues after this deadline. const SPV_STOP_TIMEOUT: Duration = Duration::from_secs(15); -/// Budget for `DashSpvClient::stop()` itself. -/// -/// dash-spv's stop joins its internal monitors, and those monitors dispatch -/// host event callbacks **synchronously** — a callback blocked in host code -/// (an FFI persister `store`, say) makes the stop unbounded. Since -/// [`SpvRuntime::stop`] sits on the path the FFI `destroy` must return -/// through, an unbounded stop hangs teardown outright instead of surfacing -/// `ErrorShutdownIncomplete`. On timeout the partially-stopped client is -/// dropped and the stop is reported as an error, so the caller treats SPV -/// as non-clean. -const SPV_CLIENT_STOP_BUDGET: Duration = Duration::from_secs(15); - -/// Post-`abort` confirmation grace for the SPV run loop. -/// -/// An abort only lands at the task's next await point, so a task parked in -/// synchronous host-callback code cannot be interrupted at all. Without this -/// bound the post-abort `handle.await` waits forever — the same hang the -/// graceful timeout above was meant to escape. -const SPV_ABORT_GRACE: Duration = Duration::from_secs(2); - -/// How often [`SpvRuntime::wait_until_ready`] re-checks for a started client -/// with connected peers. +/// How often readiness is rechecked for a client with connected peers. const SPV_READINESS_POLL_INTERVAL: Duration = Duration::from_millis(250); -/// Join a stopped SPV runner, escalating to cancellation after `timeout`. -/// -/// Returns `None` once Tokio has confirmed the task terminated. Returns -/// `Some(handle)` when it is *still live* after the post-abort grace: the -/// caller must re-park that handle (so a teardown retry re-joins it rather -/// than silently detaching a callback-capable task) and report SPV as -/// non-clean. -#[must_use = "a returned handle is a still-live task that must be re-parked and reported non-clean"] -async fn join_spv_task(handle: JoinHandle<()>, timeout: Duration) -> Option> { - join_spv_task_within(handle, timeout, SPV_ABORT_GRACE).await -} - -/// [`join_spv_task`] with an explicit post-abort grace, so tests can drive -/// the survived-the-abort path without waiting out [`SPV_ABORT_GRACE`]. -#[must_use = "a returned handle is a still-live task that must be re-parked and reported non-clean"] -async fn join_spv_task_within( - mut handle: JoinHandle<()>, - timeout: Duration, - abort_grace: Duration, -) -> Option> { - match tokio::time::timeout(timeout, &mut handle).await { - Ok(Ok(())) => None, - Ok(Err(error)) => { - tracing::warn!(?error, "SPV background run loop join error"); - None - } - Err(_) => { - tracing::warn!("SPV stop: background run loop did not unwind in time; aborting it"); - handle.abort(); - match tokio::time::timeout(abort_grace, &mut handle).await { - Ok(Err(error)) if !error.is_cancelled() => { - tracing::warn!(?error, "SPV background run loop abort join error"); - None - } - Ok(_) => None, - Err(_) => { - tracing::warn!( - "SPV stop: background run loop survived abort for {abort_grace:?}; \ - keeping the handle for a teardown retry" - ); - Some(handle) - } - } - } - } +#[derive(Default)] +enum Shutdown { + #[default] + Idle, + Running(JoinHandle>), + Failed(String), } /// SPV client runtime — owns the `DashSpvClient` and drives sync. /// /// Events are dispatched through [`PlatformEventManager`] to all registered /// handlers by reference (no cloning). +/// +/// # Lifecycle +/// +/// [`start`](Self::start) works only on a stopped runtime. [`stop`](Self::stop) +/// is idempotent; once it returns `Ok`, no SPV task runs and the storage +/// directory is released. A stop that does not complete says which way: +/// - [`ShutdownIncomplete`](PlatformWalletError::ShutdownIncomplete): teardown +/// outlived the wait and continues. Stop again; start and storage clear +/// return the same error until a stop completes. +/// - [`SpvProcessRestartRequired`](PlatformWalletError::SpvProcessRestartRequired): +/// startup or teardown panicked. This runtime refuses every later start, +/// stop and storage clear, and nothing is promised about SPV in this +/// process until it restarts. pub struct SpvRuntime { event_manager: Arc, wallet_manager: Arc>>, client: RwLock>, last_config: RwLock>, task: Mutex>>, - /// Stops currently running. A stop takes the run-loop handle out of - /// `task` before joining it, so an empty `task` does not mean the old run - /// loop has exited; `start` refuses while this is non-zero. - stops_in_progress: AtomicUsize, - /// Serializes [`stop`](Self::stop): a stop running alongside another - /// would find no client and an empty `task` while the first is still - /// joining the run loop, and report success early. - stop_serial: tokio::sync::Mutex<()>, + /// Teardown state. Held across client construction, run-loop spawning and + /// each stop or storage clear, so none of them interleave. + shutdown: tokio::sync::Mutex, peer_tracker: Arc, } -/// Counts one running [`SpvRuntime::stop`] for as long as it lives. -struct StopInProgress<'a>(&'a AtomicUsize); - -impl<'a> StopInProgress<'a> { - fn begin(count: &'a AtomicUsize) -> Self { - count.fetch_add(1, Ordering::SeqCst); - Self(count) - } -} - -impl Drop for StopInProgress<'_> { - fn drop(&mut self) { - self.0.fetch_sub(1, Ordering::SeqCst); - } -} - /// Classify a failure from the SPV acceptance-check path /// ([`SpvRuntime::broadcast_transaction_and_wait`]). /// @@ -175,28 +109,36 @@ impl SpvRuntime { client: RwLock::new(None), last_config: RwLock::new(None), task: Mutex::new(None), - stops_in_progress: AtomicUsize::new(0), - stop_serial: tokio::sync::Mutex::new(()), + shutdown: tokio::sync::Mutex::new(Shutdown::Idle), peer_tracker: Arc::new(PeerTracker::default()), } } /// Start SPV sync. pub async fn start(&self, config: ClientConfig) -> Result<(), PlatformWalletError> { + if self.client.read().await.is_some() { + return Err(PlatformWalletError::SpvAlreadyRunning); + } + // Fail fast instead of queueing behind a stop that is still joining. + self.ensure_no_live_run_loop()?; + let shutdown = self.shutdown.lock().await; { let running = self.client.read().await; if running.is_some() { return Err(PlatformWalletError::SpvAlreadyRunning); } } - self.ensure_no_live_run_loop()?; + self.ensure_joined(&shutdown)?; let network_manager = PeerNetworkManager::new(&config) .await .map_err(|e| PlatformWalletError::SpvError(e.to_string()))?; - let storage_manager = DiskStorageManager::new(&config) + let mut storage_manager = DiskStorageManager::new(&config) .await .map_err(|e| PlatformWalletError::SpvError(e.to_string()))?; + // Upstream starts the storage writer here and again in `run`, but + // stops it only for a client that ran. + StorageManager::stop(&mut storage_manager).await; // PlatformEventManager implements `EventHandler`; the peer tracker // rides alongside it so `connected_peers` can answer from the latest @@ -225,36 +167,46 @@ impl SpvRuntime { Ok(()) } - /// Refuse a start while the run loop of an earlier stop may still be live. - /// - /// A stop that is still joining the run loop has already taken it out of - /// `task`; when that loop exits, `run` clears the client, which would be - /// the one started now. [`stop`](Self::stop) also re-parks a run loop that - /// survived its abort, and [`spawn_run_loop`](Self::spawn_run_loop) keeps a - /// parked loop instead of spawning one, so a client started over it would - /// never sync. A parked loop that has exited since is dropped and the - /// start goes ahead. + /// Refuse restart until all previous startup and shutdown work is joined. fn ensure_no_live_run_loop(&self) -> Result<(), PlatformWalletError> { - // Lock `task` before reading the counter. A stop counts itself before - // it takes the handle under this lock, so a start either still sees - // the handle or already sees the stop. - let mut task = self.task.lock().expect("spv task mutex poisoned"); - if self.stops_in_progress.load(Ordering::SeqCst) > 0 { - return Err(PlatformWalletError::SpvError( - "an SPV stop is still in progress; wait for it before starting SPV".to_string(), - )); - } - match task.as_ref() { - Some(handle) if !handle.is_finished() => Err(PlatformWalletError::SpvError( - "the previous SPV run loop has not exited yet; stop SPV again before starting it" + let shutdown = self.shutdown.try_lock().map_err(|_| { + PlatformWalletError::SpvError( + "an SPV start or stop is still in progress; wait for it before starting SPV" .to_string(), - )), - Some(_) => { - *task = None; - Ok(()) + ) + })?; + self.ensure_joined(&shutdown) + } + + /// Check that nothing is left to join. Takes the locked `shutdown` state: + /// `task` only changes under that lock, so the answer cannot go stale. + fn ensure_joined(&self, shutdown: &Shutdown) -> Result<(), PlatformWalletError> { + match shutdown { + Shutdown::Idle => {} + Shutdown::Running(_) => { + return Err(PlatformWalletError::ShutdownIncomplete( + "SPV teardown is still running; stop SPV again before starting it".to_string(), + )) } - None => Ok(()), + Shutdown::Failed(error) => { + return Err(PlatformWalletError::SpvProcessRestartRequired( + error.clone(), + )) + } + } + let task = self.task_slot(); + if task.is_some() { + return Err(PlatformWalletError::ShutdownIncomplete( + "SPV startup has not been joined; stop SPV before restarting it".to_string(), + )); } + Ok(()) + } + + /// Lock the startup-task slot, recovering from poisoning: the slot is only + /// read, taken or replaced whole, so a panicking holder cannot tear it. + fn task_slot(&self) -> MutexGuard<'_, Option>> { + self.task.lock().unwrap_or_else(PoisonError::into_inner) } /// Check whether the SPV client has been started. @@ -262,6 +214,35 @@ impl SpvRuntime { self.client.try_read().map(|c| c.is_some()).unwrap_or(false) } + /// Whether SPV sync is running or starting. `false` once background sync + /// has failed, although the client stays started until [`Self::stop`]. + /// Never waits, so an event handler may call it. + pub fn is_running(&self) -> bool { + let Ok(client) = self.client.try_read() else { + return false; + }; + let Some(client) = client.as_ref() else { + return false; + }; + let starting = self + .task_slot() + .as_ref() + .is_some_and(|task| !task.is_finished()); + if starting { + return true; + } + let mut running = pin!(tokio::task::unconstrained(client.is_running())); + match running + .as_mut() + .poll(&mut Context::from_waker(Waker::noop())) + { + Poll::Ready(running) => running, + // Upstream holds this lock while it cleans up after a failed sync + // and, briefly, while it recovers from a fork. + Poll::Pending => false, + } + } + /// Whether a broadcast issued right now could reach the network: the /// client is started *and* at least one peer is connected. /// @@ -359,8 +340,7 @@ impl SpvRuntime { Ok(*quorum.quorum_entry.quorum_public_key.as_ref()) } - /// Drive the sync loop of an already-[`start`]ed client until [`stop`] - /// is called + /// Start upstream's background sync while retaining its client for queries and stop. async fn run(&self) -> Result<(), PlatformWalletError> { let client_guard = self.client.read().await; let client = client_guard @@ -376,111 +356,117 @@ impl SpvRuntime { .await .map_err(|e| PlatformWalletError::SpvError(e.to_string())); - let mut client = self.client.write().await; - let _ = client.take(); - self.peer_tracker.clear(); + self.finish_startup(result).await + } + async fn finish_startup( + &self, + result: Result<(), PlatformWalletError>, + ) -> Result<(), PlatformWalletError> { + if result.is_err() { + let client = self.client.write().await.take(); + if let Some(client) = client { + client.stop().await; + } + self.peer_tracker.clear(); + } result } - /// Stop SPV sync gracefully. Unlocks the data dir safely. + /// Stop SPV sync, waiting up to 15 seconds for startup and teardown. /// - /// **Every phase after taking the client is bounded** — `stop` runs on the - /// path [`PlatformWalletManager::shutdown`](crate::manager::PlatformWalletManager::shutdown) - /// and therefore the FFI's `destroy` must return through, so a wedged - /// host callback has to surface as an error rather than hang teardown: - /// the client stop is capped at [`SPV_CLIENT_STOP_BUDGET`], the run-loop - /// join at [`SPV_STOP_TIMEOUT`] with an [`SPV_ABORT_GRACE`] post-abort - /// confirmation. A run loop that outlives all of that is **re-parked**, - /// not detached, so a teardown retry re-joins it — and the error return - /// keeps it out of a clean shutdown verdict. + /// A timeout or cancelled caller leaves teardown tracked for the next stop. + /// Restart stays blocked until a stop confirms completion. Client queries + /// already in flight finish before teardown starts; this wait is unbounded. /// - /// Taking the client is not bounded: it waits for the client's read - /// guards, and [`broadcast_transaction_and_wait`](Self::broadcast_transaction_and_wait) - /// holds one for its whole acceptance wait (the caller's timeout). That - /// keeps a broadcast on a live client and the data directory free once - /// `stop` returns, at the cost of a stop waiting for the broadcast. + /// # Errors /// - /// Idempotent. Stops run one at a time: a second call waits for the one - /// in flight, then finds no client and re-joins whatever that one - /// re-parked, so it never reports success while the old run loop is live. + /// See the lifecycle on [`SpvRuntime`]. pub async fn stop(&self) -> Result<(), PlatformWalletError> { - let _stopping = StopInProgress::begin(&self.stops_in_progress); - let _serial = self.stop_serial.lock().await; + self.stop_with(|client| async move { client.stop().await }) + .await + } - let taken = { - let mut client = self.client.write().await; - client.take() - }; + async fn stop_with(&self, stop_client: F) -> Result<(), PlatformWalletError> + where + F: FnOnce(SpvClient) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + { + let mut shutdown = self.shutdown.lock().await; + self.stop_locked(&mut shutdown, stop_client).await + } - let stop_result = match taken { - Some(c) => match tokio::time::timeout(SPV_CLIENT_STOP_BUDGET, c.stop()).await { - Ok(result) => result.map_err(|e| PlatformWalletError::SpvError(e.to_string())), - Err(_) => { - // The client is dropped with the timed-out future. The - // data-dir lock may outlive this call, which is strictly - // better than never returning from `destroy`. - tracing::warn!( - "SPV client stop did not complete within {:?}; abandoning it", - SPV_CLIENT_STOP_BUDGET - ); - Err(PlatformWalletError::SpvError(format!( - "SPV client stop did not complete within {SPV_CLIENT_STOP_BUDGET:?}" - ))) + /// Run or rejoin teardown. The caller holds the `shutdown` lock. + async fn stop_locked( + &self, + shutdown: &mut Shutdown, + stop_client: F, + ) -> Result<(), PlatformWalletError> + where + F: FnOnce(SpvClient) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + { + if matches!(*shutdown, Shutdown::Idle) { + let client = self.client.write().await.take(); + let startup = self.task_slot().take(); + let peers = Arc::clone(&self.peer_tracker); + // Never cancel upstream run/stop: each can own callback-capable tasks + // that cancellation would detach before their handles are restored. + *shutdown = Shutdown::Running(tokio::spawn(async move { + let result = match startup { + Some(task) => task.await, + None => Ok(()), + }; + if let Some(client) = client { + stop_client(client).await; } - }, - None => Ok(()), - }; - self.peer_tracker.clear(); - - let handle = self.task.lock().expect("spv task mutex poisoned").take(); - let join_result = match handle { - None => Ok(()), - Some(handle) => match join_spv_task(handle, SPV_STOP_TIMEOUT).await { - None => Ok(()), - Some(live) => { - // Re-park rather than drop: dropping a `JoinHandle` - // detaches the task, and this one can still reach host - // callbacks. Keeping it lets a teardown retry re-join. - *self.task.lock().expect("spv task mutex poisoned") = Some(live); - Err(PlatformWalletError::SpvError( - "SPV background run loop did not terminate after abort; \ - it is still tracked for a retry" - .to_string(), + peers.clear(); + result + })); + } + + if let Shutdown::Running(task) = shutdown { + match tokio::time::timeout(SPV_STOP_TIMEOUT, task).await { + Ok(Ok(Ok(()))) => *shutdown = Shutdown::Idle, + Ok(result) => { + *shutdown = Shutdown::Failed(format!("SPV teardown task failed: {result:?}")) + } + Err(_) => { + return Err(PlatformWalletError::ShutdownIncomplete( + "SPV teardown timed out; it is still tracked for a retry".to_string(), )) } - }, - }; - - // A failed client stop is the more informative diagnosis, so it wins; - // either one makes the caller's shutdown verdict non-clean. - stop_result.and(join_result) + } + } + match shutdown { + Shutdown::Failed(error) => Err(PlatformWalletError::SpvProcessRestartRequired( + error.clone(), + )), + _ => Ok(()), + } } - /// Spawn the sync loop of an already-[`start`]ed client on the current - /// tokio runtime and return immediately. + /// Start upstream background sync on the current Tokio runtime. /// - /// Call [`stop`] to stop it + /// Skipped with a warning while a start or stop is in progress, or while + /// earlier startup or teardown is still unjoined. Call [`Self::stop`] to + /// stop it. pub fn spawn_run_loop(self: &Arc) { - { - let existing = self.task.lock().expect("spv task mutex poisoned"); - if existing.is_some() { - tracing::warn!( - "spawn_in_background called while a task is already running; ignoring" - ); - return; - } + let Ok(shutdown) = self.shutdown.try_lock() else { + tracing::warn!("SPV background sync not spawned: an SPV start or stop is in progress"); + return; + }; + if let Err(error) = self.ensure_joined(&shutdown) { + tracing::warn!(%error, "SPV background sync not spawned: earlier SPV work is unjoined"); + return; } - let this = Arc::clone(self); - - let handle = tokio::spawn(async move { + let mut task = self.task_slot(); + *task = Some(tokio::spawn(async move { if let Err(e) = this.run().await { - tracing::warn!("SpvRuntime background run loop exited with error: {}", e); + tracing::warn!("SpvRuntime background startup failed: {}", e); } - }); - - *self.task.lock().expect("spv task mutex poisoned") = Some(handle); + })); } /// The peers the SPV client is currently connected to, each classified @@ -504,16 +490,9 @@ impl SpvRuntime { return Vec::new(); } - let engine = client.masternode_list_engine().ok(); + let list = client.latest_masternode_list().await; drop(client_guard); - - match engine { - Some(engine) => { - let engine_guard = engine.read().await; - classify_peers(&addresses, engine_guard.latest_masternode_list()) - } - None => classify_peers(&addresses, None), - } + classify_peers(&addresses, list.as_ref()) } /// Snapshot of the current deterministic masternode list (DML) keyed @@ -531,18 +510,11 @@ impl SpvRuntime { pub fn masternode_validity_snapshot_blocking( &self, ) -> Option> { - // Clone the engine `Arc` out while holding the client lock, then - // drop it before reading the engine — same ordering as - // `connected_peers`. - let engine = { + let list = { let client_guard = self.client.blocking_read(); - let client = client_guard.as_ref()?; - client.masternode_list_engine().ok()? + client_guard.as_ref()?.latest_masternode_list_blocking()? }; - let engine_guard = engine.blocking_read(); - let list = engine_guard.latest_masternode_list()?; - let mut map = std::collections::HashMap::with_capacity(list.masternodes.len()); for qualified in list.masternodes.values() { let entry = &qualified.masternode_list_entry; @@ -574,55 +546,40 @@ impl SpvRuntime { /// it must run off the async runtime (FFI blocking thread), mirroring the /// other `*_blocking` accessors. pub fn masternodes_by_voting_key_blocking(&self, voting_key_id: &PubkeyHash) -> Vec<[u8; 32]> { - // Clone the engine `Arc` out while holding the client lock, then drop - // it before reading the engine — same ordering as `connected_peers`. - let engine = { + let list = { let client_guard = self.client.blocking_read(); let Some(client) = client_guard.as_ref() else { return Vec::new(); }; - match client.masternode_list_engine().ok() { - Some(engine) => engine, - None => return Vec::new(), - } - }; - - let engine_guard = engine.blocking_read(); - let Some(list) = engine_guard.latest_masternode_list() else { - return Vec::new(); + let Some(list) = client.latest_masternode_list_blocking() else { + return Vec::new(); + }; + list }; - masternodes_by_voting_key(list, voting_key_id) + masternodes_by_voting_key(&list, voting_key_id) } /// Snapshot of the current-tip deterministic masternode list as typed /// summaries. `None` when the list isn't available (SPV client not /// running, engine not initialized, or masternode sync not complete). - /// Clones the engine `Arc` out under the client lock and reads the - /// engine without it — the two never nest, same as - /// [`Self::masternode_validity_snapshot_blocking`]. + /// Reads an owned snapshot through the client's public query API. pub async fn masternode_list_summaries(&self) -> Option> { - let engine = { + let list = { let client_guard = self.client.read().await; - let client = client_guard.as_ref()?; - client.masternode_list_engine().ok()? + client_guard.as_ref()?.latest_masternode_list().await? }; - let engine_guard = engine.read().await; - let list = engine_guard.latest_masternode_list()?; - Some(MasternodeListSummary::all_from_list(list)) + Some(MasternodeListSummary::all_from_list(&list)) } /// Blocking twin of [`Self::masternode_list_summaries`] for FFI threads /// (`blocking_read`; never call from the async runtime). pub fn masternode_list_summaries_blocking(&self) -> Option> { - let engine = { + let list = { let client_guard = self.client.blocking_read(); - let client = client_guard.as_ref()?; - client.masternode_list_engine().ok()? + client_guard.as_ref()?.latest_masternode_list_blocking()? }; - let engine_guard = engine.blocking_read(); - let list = engine_guard.latest_masternode_list()?; - Some(MasternodeListSummary::all_from_list(list)) + Some(MasternodeListSummary::all_from_list(&list)) } /// Get the current sync progress. @@ -631,7 +588,7 @@ impl SpvRuntime { pub async fn sync_progress(&self) -> Option { let client_guard = self.client.read().await; let client = client_guard.as_ref()?; - Some(client.sync_progress().await) + Some(client.progress().await) } /// Read the unix-seconds block time of the SPV header storage's @@ -689,20 +646,22 @@ impl SpvRuntime { /// Clear all persisted SPV storage (headers, filters, state). /// - /// If the SPVClient is running it will be stopped and the - /// storage will be cleaned. If it is not running a tmp - /// Storage Manager built from the cached config will be used. + /// A running client is stopped first, as by [`Self::stop`], and stays + /// stopped: start SPV again to resync. Nothing is cleared unless that + /// stop completes. pub async fn clear_storage(&self) -> Result<(), PlatformWalletError> { - // Fast path: a live client holds the storage lock; clear through it. - { - let client_guard = self.client.read().await; - if let Some(client) = client_guard.as_ref() { - return client - .clear_storage() - .await - .map_err(|e| PlatformWalletError::SpvError(e.to_string())); - } - } + self.clear_storage_with(|client| async move { client.stop().await }) + .await + } + + async fn clear_storage_with(&self, stop_client: F) -> Result<(), PlatformWalletError> + where + F: FnOnce(SpvClient) -> Fut + Send + 'static, + Fut: Future + Send + 'static, + { + // Held to the end so no start can reopen the storage mid-clear. + let mut shutdown = self.shutdown.lock().await; + self.stop_locked(&mut shutdown, stop_client).await?; let config = self.last_config.read().await.clone().ok_or_else(|| { PlatformWalletError::SpvError( @@ -717,7 +676,7 @@ impl SpvRuntime { StorageManager::clear(&mut storage) .await .map_err(|e| PlatformWalletError::SpvError(e.to_string()))?; - StorageManager::shutdown(&mut storage).await; + StorageManager::stop(&mut storage).await; Ok(()) } @@ -788,7 +747,7 @@ mod masternodes_by_voting_key_tests { /// Build a list from `(proTxHash-seed, voting-key-id)` pairs so each entry /// gets a distinct proTxHash and a caller-chosen voting key. fn list_from(entries: Vec<(u8, [u8; 20])>) -> MasternodeList { - let masternodes = entries + let masternodes: dashcore::sml::masternode_list::MasternodeMap = entries .into_iter() .map(|(seed, voting_key_id)| { let mut hash_bytes = [0u8; 32]; @@ -807,12 +766,12 @@ mod masternodes_by_voting_key_tests { is_valid: true, mn_type: EntryMasternodeType::Regular, }; - (pro_tx_hash, entry.into()) + (pro_tx_hash, std::sync::Arc::new(entry.into())) }) .collect(); MasternodeList::build( masternodes, - Default::default(), + std::collections::BTreeMap::new(), BlockHash::from_byte_array([0u8; 32]), 0, ) @@ -866,72 +825,6 @@ mod masternodes_by_voting_key_tests { } } -#[cfg(test)] -mod shutdown_tests { - use super::*; - use std::sync::atomic::{AtomicBool, Ordering}; - - struct DropFlag(Arc); - - impl Drop for DropFlag { - fn drop(&mut self) { - self.0.store(true, Ordering::SeqCst); - } - } - - #[tokio::test(start_paused = true)] - async fn timed_out_spv_task_is_aborted_and_joined_before_return() { - let dropped = Arc::new(AtomicBool::new(false)); - let dropped_in_task = Arc::clone(&dropped); - let (started_tx, started_rx) = tokio::sync::oneshot::channel(); - let handle = tokio::spawn(async move { - let _flag = DropFlag(dropped_in_task); - let _ = started_tx.send(()); - std::future::pending::<()>().await; - }); - started_rx.await.expect("SPV task should start"); - - assert!( - join_spv_task(handle, SPV_STOP_TIMEOUT).await.is_none(), - "an abortable task must be confirmed terminated, not returned as live" - ); - - assert!( - dropped.load(Ordering::SeqCst), - "abort must be joined so task-owned callback state is dropped" - ); - } - - /// A run loop parked in **synchronous** code cannot be interrupted by - /// `abort` — the cancellation only lands at the next await point, which - /// never comes. The post-abort confirmation must therefore be bounded - /// and hand the still-live handle back, so `stop` can re-park it (a - /// dropped `JoinHandle` detaches the task, and this one can still reach - /// host callbacks) and report SPV non-clean. - /// - /// Without the post-abort deadline this test hangs: that is exactly the - /// hang that would reach the FFI's `destroy`. - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn spv_task_surviving_abort_is_returned_for_reparking() { - let (started_tx, started_rx) = tokio::sync::oneshot::channel(); - let handle = tokio::spawn(async move { - let _ = started_tx.send(()); - // Stands in for a monitor blocked inside a synchronous host - // callback: no await point for the abort to land on. - std::thread::sleep(Duration::from_millis(500)); - }); - started_rx.await.expect("SPV task should start"); - - let live = - join_spv_task_within(handle, Duration::from_millis(10), Duration::from_millis(20)) - .await - .expect("an un-abortable task must be handed back, never silently detached"); - - // Cleanup: the blocking section does end eventually. - let _ = live.await; - } -} - impl std::fmt::Debug for SpvRuntime { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SpvRuntime") @@ -942,7 +835,7 @@ impl std::fmt::Debug for SpvRuntime { #[cfg(test)] mod tests { - use std::sync::Arc; + use std::sync::{Arc, Mutex}; use std::time::Duration; use dash_spv::error::{NetworkError, SpvError}; @@ -953,7 +846,7 @@ mod tests { use super::{classify_spv_send_error, SpvRuntime}; use crate::broadcaster::BroadcastError; use crate::error::PlatformWalletError; - use crate::events::PlatformEventManager; + use crate::events::{EventHandler, PlatformEventHandler, PlatformEventManager}; use crate::wallet::platform_wallet::PlatformWalletInfo; use dash_spv::ClientConfig; @@ -964,9 +857,18 @@ mod tests { SpvRuntime::new(wallet_manager, Arc::new(PlatformEventManager::new(vec![]))) } - /// A stop that could not join its run loop leaves it parked. A start - /// issued while that loop is live must fail instead of starting a client - /// that `spawn_run_loop` would give no run loop. + /// Poll until `ready` holds, failing the test instead of hanging it. + async fn wait_until(what: &str, ready: impl Fn() -> bool) { + tokio::time::timeout(Duration::from_secs(5), async { + while !ready() { + tokio::task::yield_now().await; + } + }) + .await + .unwrap_or_else(|_| panic!("timed out waiting for {what}")); + } + + /// An outstanding startup must remain owned until stop joins it. #[tokio::test] async fn should_refuse_a_start_while_a_parked_run_loop_is_live() { let runtime = unstarted_runtime(); @@ -979,8 +881,8 @@ mod tests { let result = runtime.start(ClientConfig::default()).await; assert!( - matches!(result, Err(PlatformWalletError::SpvError(_))), - "a start over a live parked run loop must fail, got {result:?}" + is_retryable(&result), + "a start over a live parked run loop must ask for a stop, got {result:?}" ); assert!(!runtime.is_started(), "no client may be started"); assert!( @@ -994,9 +896,7 @@ mod tests { let _ = release_tx.send(()); } - /// A stop that is joining the run loop has taken it out of `task`. A start - /// in that window must fail: when the old loop exits, `run` clears the - /// client, which would be the newly started one. + /// Startup remains owned by teardown while its original slot is empty. #[tokio::test] async fn should_refuse_a_start_while_a_stop_is_joining_the_run_loop() { let runtime = Arc::new(unstarted_runtime()); @@ -1098,10 +998,9 @@ mod tests { .expect("the second stop finds nothing left to stop"); } - /// Once the parked run loop has exited, the next start drops it and - /// goes ahead. + /// Completion alone cannot prove success: stop must check the join result. #[tokio::test] - async fn should_drop_a_parked_run_loop_that_has_exited() { + async fn should_join_a_completed_startup_before_allowing_restart() { let runtime = unstarted_runtime(); let exited = tokio::spawn(async {}); tokio::time::timeout(Duration::from_secs(5), async { @@ -1113,6 +1012,8 @@ mod tests { .expect("an empty task should finish promptly"); *runtime.task.lock().expect("spv task mutex poisoned") = Some(exited); + assert!(runtime.ensure_no_live_run_loop().is_err()); + runtime.stop().await.unwrap(); assert!(runtime.ensure_no_live_run_loop().is_ok()); assert!( runtime @@ -1124,6 +1025,533 @@ mod tests { ); } + #[tokio::test] + async fn should_retain_client_after_successful_startup() { + let (runtime, _storage) = offline_runtime().await; + runtime.finish_startup(Ok(())).await.unwrap(); + *runtime.task.lock().unwrap() = Some(tokio::spawn(async {})); + assert!(matches!( + runtime.start(ClientConfig::default()).await, + Err(PlatformWalletError::SpvAlreadyRunning) + )); + let retained = runtime.is_started(); + let progress = runtime.sync_progress().await; + let broadcast = runtime + .broadcast_transaction_and_wait(&dummy_tx(), None) + .await; + assert!( + matches!(broadcast, Err(BroadcastError::Rejected { reason }) if reason.contains("no connected peers")), + "broadcast must reach the retained client" + ); + runtime.stop().await.unwrap(); + assert!( + retained, + "successful startup must retain access to the background client" + ); + assert!( + progress.is_some(), + "queries must remain available after startup" + ); + } + + #[tokio::test] + async fn should_track_shutdown_after_stop_caller_is_cancelled() { + let runtime = Arc::new(unstarted_runtime()); + let (release, pending) = tokio::sync::oneshot::channel(); + *runtime.task.lock().unwrap() = Some(tokio::spawn(async move { + let _ = pending.await; + })); + let stopping = { + let runtime = Arc::clone(&runtime); + tokio::spawn(async move { runtime.stop().await }) + }; + wait_until("stop to take the startup task", || { + runtime.task.lock().unwrap().is_none() + }) + .await; + stopping.abort(); + let _ = stopping.await; + let blocked = runtime.ensure_no_live_run_loop().is_err(); + let retry = tokio::time::timeout(Duration::from_millis(10), runtime.stop()).await; + let _ = release.send(()); + runtime.stop().await.unwrap(); + assert!( + blocked, + "cancelling stop must not permit a restart over live work" + ); + assert!( + retry.is_err(), + "retry must wait for the original work to finish" + ); + } + + /// Teardown is still tracked: the host should stop again. + fn is_retryable(result: &Result<(), PlatformWalletError>) -> bool { + matches!(result, Err(PlatformWalletError::ShutdownIncomplete(_))) + } + + /// Startup or teardown panicked: only a process restart recovers. + fn needs_restart(result: &Result<(), PlatformWalletError>) -> bool { + matches!( + result, + Err(PlatformWalletError::SpvProcessRestartRequired(_)) + ) + } + + async fn offline_runtime() -> (Arc, tempfile::TempDir) { + let storage = tempfile::tempdir().unwrap(); + let runtime = Arc::new(unstarted_runtime()); + runtime + .start( + ClientConfig::testnet() + .with_storage_path(storage.path()) + .with_restrict_to_configured_peers(true), + ) + .await + .unwrap(); + (runtime, storage) + } + + /// Wait out two periods of upstream's five-second storage writer and + /// check that nothing re-created the removed directory. + async fn assert_no_storage_writer_survives(storage: &tempfile::TempDir) { + std::fs::remove_dir_all(storage.path()).unwrap(); + tokio::time::sleep(Duration::from_secs(11)).await; + assert!( + !storage.path().exists(), + "a storage writer outlived its client" + ); + } + + /// Upstream starts a storage writer at construction and stops it only for + /// a client whose sync loop ran. Left behind, it keeps writing into a + /// directory that was cleared or handed to the next client. + #[tokio::test(start_paused = true)] + async fn should_leave_no_storage_writer_after_clearing_a_client_that_never_ran() { + let (runtime, storage) = offline_runtime().await; + runtime.clear_storage().await.unwrap(); + assert_no_storage_writer_survives(&storage).await; + } + + #[tokio::test(start_paused = true)] + async fn should_leave_no_storage_writer_after_client_construction_fails() { + let storage = tempfile::tempdir().unwrap(); + let mut config = ClientConfig::testnet() + .with_storage_path(storage.path()) + .with_restrict_to_configured_peers(true); + config.max_peers = 0; + assert!(unstarted_runtime().start(config).await.is_err()); + assert_no_storage_writer_survives(&storage).await; + } + + /// Drive the production startup path and wait for upstream `run` to return. + async fn run_offline(runtime: &Arc) { + runtime.spawn_run_loop(); + wait_until("the startup task to finish", || { + runtime + .task + .lock() + .unwrap() + .as_ref() + .is_some_and(|task| task.is_finished()) + }) + .await; + } + + fn offline_config(storage: &tempfile::TempDir) -> ClientConfig { + ClientConfig::testnet() + .with_storage_path(storage.path()) + .with_restrict_to_configured_peers(true) + } + + /// The production startup path: `spawn_run_loop` drives upstream `run`, + /// the client stays available for queries, and stop tears down a client + /// whose sync loop is live. + #[tokio::test] + async fn should_run_the_real_startup_and_stop_a_running_client() { + let (runtime, storage) = offline_runtime().await; + assert!( + !runtime.is_running(), + "a client that never ran is not running" + ); + + run_offline(&runtime).await; + assert!(runtime.is_started(), "startup must retain the client"); + assert!(runtime.is_running()); + assert!( + runtime.sync_progress().await.is_some(), + "queries must reach the retained client" + ); + + runtime.stop().await.unwrap(); + assert!(!runtime.is_started()); + assert!(!runtime.is_running()); + runtime + .start(offline_config(&storage)) + .await + .expect("restart after stopping a running client"); + runtime.stop().await.unwrap(); + } + + /// Upstream stops itself when its sync loop fails and reports that only + /// through `on_error`, so the retained client must stop counting as + /// running. + #[tokio::test] + async fn should_not_report_running_after_upstream_background_sync_fails() { + /// Reports the error and what a host callback sees when it queries + /// the running state from inside the callback. + struct ErrorObserver { + runtime: std::sync::Weak, + report: Mutex>>, + } + + impl EventHandler for ErrorObserver { + fn on_error(&self, error: &str) { + if let Some(report) = self.report.lock().unwrap().take() { + let running = self.runtime.upgrade().is_some_and(|spv| spv.is_running()); + let _ = report.send((error.to_owned(), running)); + } + } + } + + impl PlatformEventHandler for ErrorObserver {} + + let (runtime, storage) = offline_runtime().await; + let (sender, error) = tokio::sync::oneshot::channel(); + runtime.event_manager.add_handler(Arc::new(ErrorObserver { + runtime: Arc::downgrade(&runtime), + report: Mutex::new(Some(sender)), + })); + run_offline(&runtime).await; + assert!(runtime.is_running()); + + // Dropping the empty fixture's wallet closes its event channel. The + // real upstream monitor reports the failure and stops the sync loop. + *runtime.wallet_manager.write().await = WalletManager::new(Network::Testnet); + let (error, running_in_callback) = tokio::time::timeout(Duration::from_secs(5), error) + .await + .expect("upstream must report the background failure") + .expect("the error observer must remain registered"); + assert!( + !running_in_callback, + "a callback querying the running state must get the stopped answer" + ); + assert!( + error.contains("WalletEvent monitor channel closed unexpectedly"), + "expected a wallet event monitor failure, got {error}" + ); + + assert!(runtime.is_started(), "the client stays owned until stop"); + // Upstream cleans up after the failure in a task of its own. + for _ in 0..1000 { + assert!( + !runtime.is_running(), + "a failed sync must not report running while upstream cleans up" + ); + tokio::task::yield_now().await; + } + runtime.stop().await.unwrap(); + assert!(!runtime.is_started()); + assert!(!runtime.is_running()); + runtime + .start(offline_config(&storage)) + .await + .expect("restart after releasing the stopped client"); + run_offline(&runtime).await; + assert!(runtime.is_running()); + runtime.stop().await.unwrap(); + } + + #[tokio::test(start_paused = true)] + async fn should_keep_timed_out_upstream_stop_alive_until_retry_joins_it() { + let (runtime, storage) = offline_runtime().await; + let (release, pending) = tokio::sync::oneshot::channel(); + let stopped = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let completed = Arc::clone(&stopped); + let result = runtime + .stop_with(move |client| async move { + let _ = pending.await; + client.stop().await; + completed.store(true, std::sync::atomic::Ordering::SeqCst); + }) + .await; + assert!( + is_retryable(&result), + "an incomplete upstream stop must time out as retryable, got {result:?}" + ); + assert!(is_retryable(&runtime.start(ClientConfig::default()).await)); + assert!( + is_retryable(&runtime.clear_storage().await), + "storage must not be cleared under a pending upstream stop" + ); + assert!( + is_retryable(&runtime.stop().await), + "retry must not report clean while upstream stop is pending" + ); + release + .send(()) + .expect("timed out stop must still own its future"); + runtime.stop().await.unwrap(); + assert!(stopped.load(std::sync::atomic::Ordering::SeqCst)); + runtime + .start( + ClientConfig::testnet() + .with_storage_path(storage.path()) + .with_restrict_to_configured_peers(true), + ) + .await + .expect("restart after confirmed teardown"); + runtime.stop().await.unwrap(); + runtime.stop().await.unwrap(); + } + + #[tokio::test] + async fn should_keep_upstream_stop_alive_when_its_caller_is_cancelled() { + let (runtime, _storage) = offline_runtime().await; + let (entered, started) = tokio::sync::oneshot::channel(); + let (release, pending) = tokio::sync::oneshot::channel(); + let stopping = { + let runtime = Arc::clone(&runtime); + tokio::spawn(async move { + runtime + .stop_with(move |client| async move { + entered.send(()).unwrap(); + let _ = pending.await; + client.stop().await; + }) + .await + }) + }; + started.await.unwrap(); + stopping.abort(); + let _ = stopping.await; + assert!(runtime.start(ClientConfig::default()).await.is_err()); + assert!( + tokio::time::timeout(Duration::from_millis(10), runtime.stop()) + .await + .is_err() + ); + release + .send(()) + .expect("cancelled caller must leave upstream stop owned"); + runtime.stop().await.unwrap(); + } + + /// Clearing stops the client, so the runtime must not keep reporting it + /// as started or holding its startup task. + #[tokio::test] + async fn should_leave_spv_stopped_and_restartable_after_clearing_a_running_client() { + let (runtime, storage) = offline_runtime().await; + runtime.finish_startup(Ok(())).await.unwrap(); + *runtime.task.lock().unwrap() = Some(tokio::spawn(async {})); + + runtime.clear_storage().await.unwrap(); + + assert!( + !runtime.is_started(), + "a cleared client is stopped and must not be reported as started" + ); + runtime + .start( + ClientConfig::testnet() + .with_storage_path(storage.path()) + .with_restrict_to_configured_peers(true), + ) + .await + .expect("restart after clearing a running client"); + runtime.stop().await.unwrap(); + } + + #[tokio::test] + async fn should_keep_teardown_tracked_when_a_clear_storage_caller_is_cancelled() { + let (runtime, storage) = offline_runtime().await; + let (entered, started) = tokio::sync::oneshot::channel(); + let (release, pending) = tokio::sync::oneshot::channel(); + let clearing = { + let runtime = Arc::clone(&runtime); + tokio::spawn(async move { + runtime + .clear_storage_with(move |client| async move { + entered.send(()).unwrap(); + let _ = pending.await; + client.stop().await; + }) + .await + }) + }; + started.await.unwrap(); + clearing.abort(); + let _ = clearing.await; + assert!(runtime.start(ClientConfig::default()).await.is_err()); + assert!( + tokio::time::timeout(Duration::from_millis(10), runtime.clear_storage()) + .await + .is_err(), + "a retried clear must wait for the teardown it left running" + ); + release + .send(()) + .expect("cancelled caller must leave upstream stop owned"); + runtime.clear_storage().await.unwrap(); + runtime + .start( + ClientConfig::testnet() + .with_storage_path(storage.path()) + .with_restrict_to_configured_peers(true), + ) + .await + .expect("restart after the retried clear joins teardown"); + runtime.stop().await.unwrap(); + } + + #[tokio::test] + async fn should_wait_for_startup_before_stopping_its_client() { + let (runtime, _storage) = offline_runtime().await; + let (release, pending) = tokio::sync::oneshot::channel(); + let startup_done = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let completed = Arc::clone(&startup_done); + *runtime.task.lock().unwrap() = Some(tokio::spawn(async move { + let _ = pending.await; + completed.store(true, std::sync::atomic::Ordering::SeqCst); + })); + let stopping = { + let runtime = Arc::clone(&runtime); + tokio::spawn(async move { + runtime + .stop_with(move |client| async move { + assert!(startup_done.load(std::sync::atomic::Ordering::SeqCst)); + client.stop().await; + }) + .await + }) + }; + wait_until("stop to take the startup task", || { + runtime.task.lock().unwrap().is_none() + }) + .await; + assert!(!stopping.is_finished()); + runtime.spawn_run_loop(); + assert!( + runtime.task.lock().unwrap().is_none(), + "cannot spawn during teardown" + ); + release.send(()).unwrap(); + stopping.await.unwrap().unwrap(); + } + + /// Failed-startup cleanup runs inside the startup task, whose finished + /// handle stays owned until stop joins it. + #[tokio::test] + async fn should_clean_up_failed_startup_and_allow_restart_after_stop() { + let (runtime, storage) = offline_runtime().await; + let config = || { + ClientConfig::testnet() + .with_storage_path(storage.path()) + .with_restrict_to_configured_peers(true) + }; + let startup = { + let runtime = Arc::clone(&runtime); + tokio::spawn(async move { + let failure = PlatformWalletError::SpvError("mock startup failure".into()); + assert!(runtime.finish_startup(Err(failure)).await.is_err()); + }) + }; + *runtime.task.lock().unwrap() = Some(startup); + wait_until("the failed startup to finish its cleanup", || { + runtime + .task + .lock() + .unwrap() + .as_ref() + .is_some_and(|task| task.is_finished()) + }) + .await; + + assert!(!runtime.is_started(), "failed startup must drop its client"); + assert!( + is_retryable(&runtime.start(config()).await), + "an unjoined startup must block restart until a stop joins it" + ); + runtime.stop().await.unwrap(); + runtime + .start(config()) + .await + .expect("restart after stop joins the failed startup"); + runtime.stop().await.unwrap(); + } + + #[tokio::test] + async fn should_never_report_clean_after_a_startup_task_panics() { + let runtime = Arc::new(unstarted_runtime()); + *runtime.task.lock().unwrap() = Some(tokio::spawn(async { panic!("mock startup panic") })); + assert!(needs_restart(&runtime.stop().await)); + assert!( + needs_restart(&runtime.stop().await), + "a join failure cannot be forgotten by a retry" + ); + assert!(needs_restart(&runtime.start(ClientConfig::default()).await)); + assert!(needs_restart(&runtime.clear_storage().await)); + runtime.spawn_run_loop(); + assert!( + runtime.task.lock().unwrap().is_none(), + "no run loop may be spawned over a panicked startup" + ); + } + + #[tokio::test] + async fn should_preserve_ownership_when_cancelled_while_waiting_for_client_access() { + let (runtime, _storage) = offline_runtime().await; + let guard = runtime.client.read().await; + let stopping = { + let runtime = Arc::clone(&runtime); + tokio::spawn(async move { runtime.stop().await }) + }; + // Stop takes `shutdown` and then parks on the client write lock. + wait_until("stop to take the shutdown lock", || { + runtime.shutdown.try_lock().is_err() + }) + .await; + stopping.abort(); + let _ = stopping.await; + assert!( + guard.is_some(), + "a cancelled lock wait must not take the client" + ); + drop(guard); + runtime.stop().await.unwrap(); + assert!(!runtime.is_started()); + } + + #[tokio::test] + async fn should_keep_teardown_panics_non_clean_on_every_retry() { + let (runtime, _storage) = offline_runtime().await; + let failed = runtime + .stop_with(|_client| async { panic!("mock teardown panic") }) + .await; + assert!(needs_restart(&failed)); + assert!(needs_restart(&runtime.stop().await)); + assert!(needs_restart(&runtime.start(ClientConfig::default()).await)); + assert!(needs_restart(&runtime.clear_storage().await)); + } + + /// A panic under the `task` mutex must not make every later lifecycle + /// call panic too: teardown has to stay reachable. + #[tokio::test] + async fn should_keep_lifecycle_usable_after_the_task_mutex_is_poisoned() { + let runtime = Arc::new(unstarted_runtime()); + let poisoner = Arc::clone(&runtime); + let _ = std::thread::spawn(move || { + let _guard = poisoner.task.lock().unwrap(); + panic!("mock panic while holding the task mutex"); + }) + .join(); + assert!(runtime.task.is_poisoned()); + + runtime.spawn_run_loop(); + runtime.stop().await.unwrap(); + assert!(runtime.ensure_no_live_run_loop().is_ok()); + } + /// A minimal valid transaction — the unstarted-client arm never /// inspects it. fn dummy_tx() -> dashcore::Transaction { diff --git a/packages/rs-platform-wallet/src/test_support.rs b/packages/rs-platform-wallet/src/test_support.rs index b9446432ee0..63e42594fc6 100644 --- a/packages/rs-platform-wallet/src/test_support.rs +++ b/packages/rs-platform-wallet/src/test_support.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use async_trait::async_trait; use dashcore::hashes::Hash; -use dashcore::secp256k1::{ecdsa, Message, PublicKey, Secp256k1}; +use dashcore::secp256k1::{ecdsa, Message, PublicKey}; use dashcore::BlockHash; #[cfg(test)] use dashcore::Txid; @@ -135,25 +135,20 @@ impl Signer for WalletSigner { path: &DerivationPath, sighash: [u8; 32], ) -> Result<(ecdsa::Signature, PublicKey), Self::Error> { - let secp = Secp256k1::new(); let key = self .wallet .derive_private_key(path) .map_err(|e| e.to_string())?; let message = Message::from_digest(sighash); - Ok(( - secp.sign_ecdsa(&message, &key), - PublicKey::from_secret_key(&secp, &key), - )) + Ok((key.sign_ecdsa(message), PublicKey::from_secret_key(&key))) } async fn public_key(&self, path: &DerivationPath) -> Result { - let secp = Secp256k1::new(); let key = self .wallet .derive_private_key(path) .map_err(|e| e.to_string())?; - Ok(PublicKey::from_secret_key(&secp, &key)) + Ok(PublicKey::from_secret_key(&key)) } } diff --git a/packages/rs-platform-wallet/src/wallet/core/sign_message.rs b/packages/rs-platform-wallet/src/wallet/core/sign_message.rs index 65321a9d099..9316053883b 100644 --- a/packages/rs-platform-wallet/src/wallet/core/sign_message.rs +++ b/packages/rs-platform-wallet/src/wallet/core/sign_message.rs @@ -46,7 +46,7 @@ use std::str::FromStr; use dashcore::address::Payload; use dashcore::hashes::Hash; use dashcore::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; -use dashcore::secp256k1::{Message, Secp256k1}; +use dashcore::secp256k1::Message; use dashcore::sign_message::{signed_msg_hash, MessageSignature}; use dashcore::{Address as DashAddress, AddressType, PublicKey as DashPublicKey}; use key_wallet::managed_account::managed_account_trait::ManagedAccountTrait; @@ -274,10 +274,6 @@ impl CoreWallet { }); } - // `recover_ecdsa` needs only a `Verification` context — the signing - // tables a full `Secp256k1::new()` would also allocate are dead weight - // here, since the signature itself came from the signer. - let secp = Secp256k1::verification_only(); let digest = Message::from_digest(hash.to_byte_array()); let compact = signature.serialize_compact(); @@ -286,7 +282,8 @@ impl CoreWallet { .filter_map(|id| RecoveryId::try_from(id).ok()) .filter_map(|recid| RecoverableSignature::from_compact(&compact, recid).ok()) .find(|candidate| { - secp.recover_ecdsa(&digest, candidate) + candidate + .recover_ecdsa(digest) .is_ok_and(|recovered| recovered == public_key) }) .ok_or_else(|| PlatformWalletError::MessageSigningFailed { @@ -303,7 +300,7 @@ mod tests { use std::str::FromStr; use std::sync::Arc; - use dashcore::secp256k1::{ecdsa, PublicKey, Secp256k1}; + use dashcore::secp256k1::{ecdsa, PublicKey}; use dashcore::sign_message::{signed_msg_hash, MessageSignature}; use dashcore::{Address as DashAddress, Network}; use key_wallet::signer::{Signer, SignerMethod, TransactionCategory}; @@ -349,7 +346,7 @@ mod tests { fn verifies_for(signature_base64: &str, address: &DashAddress) -> bool { MessageSignature::from_base64(signature_base64) .expect("signature is valid base64 of a 65-byte recoverable signature") - .is_signed_by_address(&Secp256k1::new(), address, signed_msg_hash(MESSAGE)) + .is_signed_by_address(address, signed_msg_hash(MESSAGE)) .expect("P2PKH address is a supported verification target") } @@ -403,7 +400,7 @@ mod tests { assert!( MessageSignature::from_base64(&signature) .expect("valid base64 of a 65-byte recoverable signature") - .is_signed_by_address(&Secp256k1::new(), &address, signed_msg_hash("")) + .is_signed_by_address(&address, signed_msg_hash("")) .expect("P2PKH address is a supported verification target"), "a signature over the empty message must verify against signed_msg_hash(\"\")" ); diff --git a/packages/rs-platform-wallet/src/wallet/core_address_key.rs b/packages/rs-platform-wallet/src/wallet/core_address_key.rs index f7cbc1e8299..7adb517e769 100644 --- a/packages/rs-platform-wallet/src/wallet/core_address_key.rs +++ b/packages/rs-platform-wallet/src/wallet/core_address_key.rs @@ -22,7 +22,6 @@ use std::str::FromStr; -use dashcore::secp256k1::Secp256k1; use dashcore::{Address, PrivateKey as DashPrivateKey}; use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; use zeroize::Zeroizing; @@ -115,13 +114,12 @@ impl PlatformWallet { // Derive the raw scalar from the selected key source. let secret_bytes: Zeroizing<[u8; 32]> = match resolved_master { Some(master) => { - let secp = Secp256k1::new(); - let derived = master.derive_priv(&secp, &path).map_err(|e| { + let derived = master.derive_priv(&path).map_err(|e| { PlatformWalletError::KeyDerivation(format!( "failed to derive private key at {path}: {e}" )) })?; - Zeroizing::new(derived.private_key.secret_bytes()) + Zeroizing::new(derived.private_key.to_secret_bytes()) } None => { // Resident key-bearing wallet — derive from its own root. @@ -133,15 +131,15 @@ impl PlatformWallet { "failed to derive private key at {path} from resident wallet: {e}" )) })?; - Zeroizing::new(secret_key.secret_bytes()) + Zeroizing::new(secret_key.to_secret_bytes()) } }; - // Build the network-aware compressed WIF. `SecretKey::from_slice` + // Build the network-aware compressed WIF. `SecretKey::from_secret_bytes` // over the just-derived bytes is infallible, but map its error // rather than unwrap to keep the boundary panic-free. - let secret_key = dashcore::secp256k1::SecretKey::from_slice(secret_bytes.as_ref()) - .map_err(|e| { + let secret_key = + dashcore::secp256k1::SecretKey::from_secret_bytes(*secret_bytes).map_err(|e| { PlatformWalletError::KeyDerivation(format!( "derived private key bytes were not a valid secp256k1 scalar: {e}" )) diff --git a/packages/rs-platform-wallet/src/wallet/identity/crypto/auto_accept.rs b/packages/rs-platform-wallet/src/wallet/identity/crypto/auto_accept.rs index 0f93f00bff8..b7f142ff1a0 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/crypto/auto_accept.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/crypto/auto_accept.rs @@ -25,7 +25,7 @@ //! `m/9'/coin'/16'/timestamp'` (all segments hardened) use dashcore::hashes::{sha256, Hash, HashEngine}; -use dashcore::secp256k1::{ecdsa::Signature, Message, Secp256k1, SecretKey}; +use dashcore::secp256k1::{ecdsa::Signature, Message, SecretKey}; use dpp::prelude::Identifier; use key_wallet::bip32::{ChildNumber, DerivationPath}; use key_wallet::dip9::{ @@ -115,9 +115,9 @@ pub fn derive_auto_accept_private_key( PlatformWalletError::InvalidIdentityData(format!("Failed to derive auto-accept key: {}", e)) })?; - let secret_bytes = zeroize::Zeroizing::new(ext_priv.private_key.secret_bytes()); + let secret_bytes = zeroize::Zeroizing::new(ext_priv.private_key.to_secret_bytes()); - SecretKey::from_slice(&*secret_bytes).map_err(|e| { + SecretKey::from_secret_bytes(*secret_bytes).map_err(|e| { PlatformWalletError::InvalidIdentityData(format!( "Invalid derived auto-accept private key: {}", e @@ -150,8 +150,7 @@ pub fn sign_auto_accept_proof( let msg_hash = build_message_hash(sender_id, recipient_id, account_reference); let message = Message::from_digest(msg_hash); - let secp = Secp256k1::new(); - let signature = secp.sign_ecdsa(&message, secret_key); + let signature = secret_key.sign_ecdsa(message); let sig_bytes = signature.serialize_compact(); let mut proof = Vec::with_capacity(1 + 4 + 1 + 64); @@ -243,9 +242,7 @@ pub fn verify_auto_accept_proof_with_pubkey( Err(_) => return false, }; - Secp256k1::new() - .verify_ecdsa(&message, &signature, pubkey) - .is_ok() + signature.verify(message, pubkey).is_ok() } /// Verify an auto-accept proof by re-deriving the expected key from `wallet`. @@ -268,7 +265,7 @@ pub fn verify_auto_accept_proof( return Ok(false); }; let secret_key = derive_auto_accept_private_key(wallet, network, timestamp)?; - let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&Secp256k1::new(), &secret_key); + let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&secret_key); Ok(verify_auto_accept_proof_with_pubkey( &pubkey, proof_bytes, @@ -297,7 +294,7 @@ pub fn encode_auto_accept_key_blob(secret_key: &SecretKey, expiry: u32) -> Vec Result<(SecretKey, u32), Plat if blob[5] != ECDSA_KEY_SIZE { return Err(invalid("auto-accept key size must be 32")); } - let secret_key = SecretKey::from_slice(&blob[6..KEY_BLOB_LEN]) + let secret_key = <[u8; 32]>::try_from(&blob[6..KEY_BLOB_LEN]) + .map_err(|_| dashcore::secp256k1::Error::InvalidSecretKey) + .and_then(SecretKey::from_secret_bytes) .map_err(|e| invalid(format!("invalid auto-accept private key: {e}")))?; Ok((secret_key, expiry)) } @@ -574,7 +573,7 @@ mod tests { assert_eq!(proof.len(), 70); assert_eq!(auto_accept_proof_expiry(&proof), Some(expiry)); - let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&Secp256k1::new(), &owner_key); + let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&owner_key); assert!( verify_auto_accept_proof_with_pubkey(&pubkey, &proof, &scanner, &owner, account_ref), "owner verifies the scanner's proof against its own re-derived pubkey" @@ -596,14 +595,14 @@ mod tests { #[test] fn key_blob_round_trip_and_rejects_malformed() { - let key = SecretKey::from_slice(&[0x07u8; 32]).unwrap(); + let key = SecretKey::from_secret_bytes([0x07u8; 32]).unwrap(); let blob = encode_auto_accept_key_blob(&key, 12345); assert_eq!(blob.len(), 38); assert_eq!(blob[0], 0x00); // key type assert_eq!(blob[5], 0x20); // key size let (k2, e2) = decode_auto_accept_key_blob(&blob).expect("decode"); - assert_eq!(k2.secret_bytes(), key.secret_bytes()); + assert_eq!(k2.to_secret_bytes(), key.to_secret_bytes()); assert_eq!(e2, 12345); assert!(decode_auto_accept_key_blob(&blob[..37]).is_err(), "short"); @@ -617,7 +616,7 @@ mod tests { #[test] fn uri_round_trip_and_rejects_malformed() { - let key = SecretKey::from_slice(&[0x09u8; 32]).unwrap(); + let key = SecretKey::from_secret_bytes([0x09u8; 32]).unwrap(); let blob = encode_auto_accept_key_blob(&key, 999); let uri = encode_dashpay_contact_uri("bobspizza", &blob); assert!(uri.starts_with("dash:?du=bobspizza&dapk=")); @@ -665,7 +664,7 @@ mod tests { ); // The cap must not reject a normal-length (valid) dapk. - let key = SecretKey::from_slice(&[0x09u8; 32]).unwrap(); + let key = SecretKey::from_secret_bytes([0x09u8; 32]).unwrap(); let blob = encode_auto_accept_key_blob(&key, 1); let uri = encode_dashpay_contact_uri("alice", &blob); assert!( @@ -676,8 +675,8 @@ mod tests { #[test] fn verify_with_pubkey_rejects_truncated_and_no_expiry() { - let key = SecretKey::from_slice(&[0x05u8; 32]).unwrap(); - let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&Secp256k1::new(), &key); + let key = SecretKey::from_secret_bytes([0x05u8; 32]).unwrap(); + let pubkey = dashcore::secp256k1::PublicKey::from_secret_key(&key); let (s, r) = test_ids(); assert!(!verify_auto_accept_proof_with_pubkey( &pubkey, &[0u8; 3], &s, &r, 0 diff --git a/packages/rs-platform-wallet/src/wallet/identity/crypto/contact_info.rs b/packages/rs-platform-wallet/src/wallet/identity/crypto/contact_info.rs index 89047507b1a..4c5a02a20c6 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/crypto/contact_info.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/crypto/contact_info.rs @@ -121,7 +121,7 @@ pub fn derive_contact_info_keys( "Failed to derive contactInfo key: {e}" )) })?; - Ok(Zeroizing::new(ext.private_key.secret_bytes())) + Ok(Zeroizing::new(ext.private_key.to_secret_bytes())) }; Ok(ContactInfoKeys { diff --git a/packages/rs-platform-wallet/src/wallet/identity/crypto/dip14.rs b/packages/rs-platform-wallet/src/wallet/identity/crypto/dip14.rs index bd914b93606..b6772f0bf92 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/crypto/dip14.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/crypto/dip14.rs @@ -26,7 +26,6 @@ //! - [DIP-14](https://github.com/dashpay/dips/blob/master/dip-0014.md) //! - [DIP-15](https://github.com/dashpay/dips/blob/master/dip-0015.md) -use dashcore::secp256k1::Secp256k1; use dashcore::{Address, Network, PublicKey}; use dpp::prelude::Identifier; use key_wallet::account::AccountType; @@ -210,13 +209,11 @@ pub fn derive_contact_payment_address( index: u32, network: Network, ) -> Result { - let secp = Secp256k1::new(); - let child_number = ChildNumber::from_normal_idx(index).map_err(|e| { PlatformWalletError::InvalidIdentityData(format!("Invalid payment address index: {}", e)) })?; - let address_key = contact_xpub.ckd_pub(&secp, child_number).map_err(|e| { + let address_key = contact_xpub.ckd_pub(child_number).map_err(|e| { PlatformWalletError::InvalidIdentityData(format!( "Failed to derive contact payment key at index {}: {}", index, e diff --git a/packages/rs-platform-wallet/src/wallet/identity/crypto/invitation.rs b/packages/rs-platform-wallet/src/wallet/identity/crypto/invitation.rs index bbd39177c4e..884d3ce4292 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/crypto/invitation.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/crypto/invitation.rs @@ -66,7 +66,7 @@ //! the plaintext `pk` to AppsFlyer's servers. We parse that host but never //! emit it. -use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use dashcore::secp256k1::{PublicKey, SecretKey}; use dashcore::transaction::special_transaction::TransactionPayload; use dashcore::{Network, PrivateKey, ScriptBuf, Transaction, TxOut}; use dpp::prelude::AssetLockProof; @@ -185,8 +185,7 @@ fn invalid(msg: impl Into) -> PlatformWalletError { /// is the selector that binds the voucher key to its funded credit output. /// `pub(crate)` so the claim-side proof-assembly tests can build a funding tx. pub(crate) fn voucher_credit_script(voucher_key: &SecretKey) -> ScriptBuf { - let secp = Secp256k1::new(); - let pubkey = PublicKey::from_secret_key(&secp, voucher_key); + let pubkey = PublicKey::from_secret_key(voucher_key); let hash = dashcore::PublicKey::new(pubkey).pubkey_hash(); ScriptBuf::new_p2pkh(&hash) } @@ -576,7 +575,7 @@ mod tests { use dpp::identity::state_transition::asset_lock_proof::InstantAssetLockProof; fn voucher() -> SecretKey { - SecretKey::from_slice(&[0x11u8; 32]).expect("valid scalar") + SecretKey::from_secret_bytes([0x11u8; 32]).expect("valid scalar") } fn inviter_info() -> InviterInfo { @@ -590,7 +589,7 @@ mod tests { /// Build an asset-lock tx whose credit output at `index` pays the voucher /// key (and `index` decoy outputs before it that do not). fn asset_lock_tx_paying_voucher_at(key: &SecretKey, index: usize) -> Transaction { - let decoy = SecretKey::from_slice(&[0x22u8; 32]).unwrap(); + let decoy = SecretKey::from_secret_bytes([0x22u8; 32]).unwrap(); let mut credit_outputs = Vec::new(); for _ in 0..index { credit_outputs.push(TxOut { @@ -628,7 +627,7 @@ mod tests { let decoded = PrivateKey::from_wif(&wif).expect("wif decodes"); assert!(decoded.compressed, "voucher WIF must be compressed"); assert_eq!(decoded.network, network, "network preserved"); - assert_eq!(decoded.inner.secret_bytes(), voucher().secret_bytes()); + assert_eq!(decoded.inner.to_secret_bytes(), voucher().to_secret_bytes()); // Network byte matches bitcoinj/legacy (0xCC mainnet, 0xEF testnet). let raw = bs58::decode(&wif).into_vec().unwrap(); assert_eq!(raw[0], first_byte); @@ -644,7 +643,10 @@ mod tests { assert!(uri.starts_with("dashpay://invite?")); let parsed = parse_invitation_uri(&uri).expect("parse"); - assert_eq!(parsed.voucher_key.secret_bytes(), voucher().secret_bytes()); + assert_eq!( + parsed.voucher_key.to_secret_bytes(), + voucher().to_secret_bytes() + ); assert_eq!(parsed.inviter, Some(info)); assert!(parsed.islock_hex.is_some()); // The parsed txid matches the proof's transaction id (big-endian). @@ -818,7 +820,7 @@ mod tests { #[test] fn voucher_output_index_rejects_no_match() { let key = voucher(); - let other = SecretKey::from_slice(&[0x33u8; 32]).unwrap(); + let other = SecretKey::from_secret_bytes([0x33u8; 32]).unwrap(); let tx = asset_lock_tx_paying_voucher_at(&other, 0); let err = voucher_output_index(&tx, &key).unwrap_err(); assert!(err.to_string().contains("does not control")); @@ -858,7 +860,7 @@ mod tests { fn parse_rejects_duplicate_required_keys() { let wif = PrivateKey::new(voucher(), Network::Testnet).to_wif(); let other = PrivateKey::new( - SecretKey::from_slice(&[0x44u8; 32]).unwrap(), + SecretKey::from_secret_bytes([0x44u8; 32]).unwrap(), Network::Testnet, ) .to_wif(); diff --git a/packages/rs-platform-wallet/src/wallet/identity/crypto/validation.rs b/packages/rs-platform-wallet/src/wallet/identity/crypto/validation.rs index 374bd425440..c14757f7d56 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/crypto/validation.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/crypto/validation.rs @@ -851,12 +851,11 @@ mod tests { /// Derive the compressed secp256k1 pubkey (`[u8; 33]`) for a fixed /// in-range scalar — the shape a breadcrumb re-derivation produces. fn fixed_scalar_and_compressed_pubkey() -> ([u8; 32], [u8; 33]) { - use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use dashcore::secp256k1::{PublicKey, SecretKey}; let mut scalar = [0u8; 32]; scalar[31] = 7; - let secp = Secp256k1::new(); - let sk = SecretKey::from_slice(&scalar).expect("in-range scalar"); - let pubkey = PublicKey::from_secret_key(&secp, &sk).serialize(); + let sk = SecretKey::from_secret_bytes(scalar).expect("in-range scalar"); + let pubkey = PublicKey::from_secret_key(&sk).serialize(); (scalar, pubkey) } diff --git a/packages/rs-platform-wallet/src/wallet/identity/network/contact_info.rs b/packages/rs-platform-wallet/src/wallet/identity/network/contact_info.rs index 5e10146523b..690875080a3 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/network/contact_info.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/network/contact_info.rs @@ -515,7 +515,7 @@ impl DashPayView<'_, B> { S: Signer + Send + Sync, C: super::ContactCryptoProvider + Sync, { - use dashcore::secp256k1::rand::{thread_rng, RngCore}; + use dashcore::secp256k1::rand::{rng, RngCore}; use dpp::data_contract::accessors::v0::DataContractV0Getters; // Build the decrypted-payload struct once: it is both the local @@ -700,7 +700,7 @@ impl DashPayView<'_, B> { write_root_id, )?; let mut iv = [0u8; 16]; - thread_rng().fill_bytes(&mut iv); + rng().fill_bytes(&mut iv); let sealed = crypto .contact_info_seal( &root_path, diff --git a/packages/rs-platform-wallet/src/wallet/identity/network/contact_requests.rs b/packages/rs-platform-wallet/src/wallet/identity/network/contact_requests.rs index 2e2f2ef9992..79cf0857bbf 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/network/contact_requests.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/network/contact_requests.rs @@ -242,7 +242,7 @@ impl ContactCryptoProvider for SeedCryptoProvider { PlatformWalletError::InvalidIdentityData(format!("test accountRef derive: {e}")) })?; Ok(platform_encryption::calculate_account_reference( - &xprv.private_key.secret_bytes(), + &xprv.private_key.to_secret_bytes(), compact_xpub, account_index, version, @@ -260,7 +260,7 @@ impl ContactCryptoProvider for SeedCryptoProvider { })?; Ok(platform_encryption::unmask_account_reference( account_reference, - &xprv.private_key.secret_bytes(), + &xprv.private_key.to_secret_bytes(), compact_xpub, )) } @@ -341,7 +341,7 @@ impl SeedCryptoProvider { .map_err(|e| { PlatformWalletError::InvalidIdentityData(format!("test contactInfo derive: {e}")) })?; - Ok(xprv.private_key.secret_bytes()) + Ok(xprv.private_key.to_secret_bytes()) } } @@ -5576,7 +5576,7 @@ mod contact_info_provider_tests { /// the resident `derive_shared_key_ecdh` at the same path. #[tokio::test] async fn ecdh_shared_secret_returns_zeroizing_matching_resident_derivation() { - use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; + use dashcore::secp256k1::{PublicKey, SecretKey}; let seed = Mnemonic::from_phrase(PHRASE) .expect("valid mnemonic") @@ -5588,8 +5588,7 @@ mod contact_info_provider_tests { .expect("auth path"); let peer = PublicKey::from_secret_key( - &Secp256k1::new(), - &SecretKey::from_slice(&[0x42u8; 32]).expect("peer secret"), + &SecretKey::from_secret_bytes([0x42u8; 32]).expect("peer secret"), ); let provider = SeedCryptoProvider::from_seed(seed, network); diff --git a/packages/rs-platform-wallet/src/wallet/identity/network/discovery.rs b/packages/rs-platform-wallet/src/wallet/identity/network/discovery.rs index 6117a170f7b..8b3c33e150d 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/network/discovery.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/network/discovery.rs @@ -304,7 +304,7 @@ impl IdentityWallet { { candidate_scalars.insert( *key_id, - zeroize::Zeroizing::new(xpriv.private_key.secret_bytes()), + zeroize::Zeroizing::new(xpriv.private_key.to_secret_bytes()), ); } } diff --git a/packages/rs-platform-wallet/src/wallet/identity/network/identity_handle.rs b/packages/rs-platform-wallet/src/wallet/identity/network/identity_handle.rs index 402b6930737..7b568a5bf78 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/network/identity_handle.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/network/identity_handle.rs @@ -147,7 +147,6 @@ pub fn derive_ecdsa_identity_auth_keypair_from_master( identity_index: u32, key_index: u32, ) -> Result { - use dashcore::secp256k1::Secp256k1; use key_wallet::bip32::ExtendedPubKey; let path = identity_auth_derivation_path_for_type( @@ -156,7 +155,6 @@ pub fn derive_ecdsa_identity_auth_keypair_from_master( identity_index, key_index, )?; - let secp = Secp256k1::new(); // `ExtendedPrivKey` doesn't implement `Zeroize`, so we can't // wrap it in `Zeroizing` directly — but its inner // `secp256k1::SecretKey` does implement `Drop` with a memzero, @@ -167,16 +165,16 @@ pub fn derive_ecdsa_identity_auth_keypair_from_master( // returned `private_key` is wrapped in `Zeroizing` below so // the 32-byte scalar copy crossing the function boundary is // also scrubbed on the caller's drop. - let derived = master.derive_priv(&secp, &path).map_err(|e| { + let derived = master.derive_priv(&path).map_err(|e| { PlatformWalletError::InvalidIdentityData(format!( "Failed to derive private key at (identity={identity_index}, key={key_index}): {e}" )) })?; - let extended_pub = ExtendedPubKey::from_priv(&secp, &derived); + let extended_pub = ExtendedPubKey::from_priv(&derived); Ok(DerivedIdentityAuthKey { derivation_path: path, - private_key: Zeroizing::new(derived.private_key.secret_bytes()), + private_key: Zeroizing::new(derived.private_key.to_secret_bytes()), public_key: extended_pub.public_key.serialize(), }) } @@ -196,7 +194,6 @@ pub fn derive_identity_auth_keypair( identity_index: u32, key_index: u32, ) -> Result<(DerivationPath, ExtendedPrivKey, PublicKey), PlatformWalletError> { - use dashcore::secp256k1::Secp256k1; use key_wallet::bip32::ExtendedPubKey; let full_path = identity_auth_derivation_path(network, identity_index, key_index)?; @@ -209,8 +206,7 @@ pub fn derive_identity_auth_keypair( )) })?; - let secp = Secp256k1::new(); - let extended_pub = ExtendedPubKey::from_priv(&secp, &auth_key); + let extended_pub = ExtendedPubKey::from_priv(&auth_key); Ok((full_path, auth_key, extended_pub.public_key)) } @@ -434,7 +430,7 @@ impl IdentityWallet { )) })?; - Ok(Zeroizing::new(secret_key.secret_bytes())) + Ok(Zeroizing::new(secret_key.to_secret_bytes())) } /// Get a read-lock handle to the shared [`WalletManager`]. diff --git a/packages/rs-platform-wallet/src/wallet/identity/network/invitation.rs b/packages/rs-platform-wallet/src/wallet/identity/network/invitation.rs index 389846a8e20..b92ca970f19 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/network/invitation.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/network/invitation.rs @@ -1146,7 +1146,7 @@ mod tests { use dpp::dashcore::{Network, TxOut}; fn voucher_secret() -> SecretKey { - SecretKey::from_slice(&[0x11u8; 32]).unwrap() + SecretKey::from_secret_bytes([0x11u8; 32]).unwrap() } /// An asset-lock tx whose single credit output pays the voucher key. @@ -1277,7 +1277,7 @@ mod tests { #[test] fn prospective_id_follows_the_selected_credit_output() { let key = voucher_secret(); - let decoy = SecretKey::from_slice(&[0x22u8; 32]).unwrap(); + let decoy = SecretKey::from_secret_bytes([0x22u8; 32]).unwrap(); let payload = AssetLockPayload { version: 1, credit_outputs: vec![ diff --git a/packages/rs-platform-wallet/src/wallet/identity/network/payments.rs b/packages/rs-platform-wallet/src/wallet/identity/network/payments.rs index dc6a1e8c4df..994967f0d60 100644 --- a/packages/rs-platform-wallet/src/wallet/identity/network/payments.rs +++ b/packages/rs-platform-wallet/src/wallet/identity/network/payments.rs @@ -1938,10 +1938,9 @@ mod tests { .wallet .derive_extended_private_key(path) .map_err(|e| e.to_string())?; - let secp = dashcore::secp256k1::Secp256k1::new(); let msg = dashcore::secp256k1::Message::from_digest(sighash); - let sig = secp.sign_ecdsa(&msg, &xprv.private_key); - let pk = dashcore::secp256k1::PublicKey::from_secret_key(&secp, &xprv.private_key); + let sig = xprv.private_key.sign_ecdsa(msg); + let pk = dashcore::secp256k1::PublicKey::from_secret_key(&xprv.private_key); Ok((sig, pk)) } @@ -1953,9 +1952,7 @@ mod tests { .wallet .derive_extended_private_key(path) .map_err(|e| e.to_string())?; - let secp = dashcore::secp256k1::Secp256k1::new(); Ok(dashcore::secp256k1::PublicKey::from_secret_key( - &secp, &xprv.private_key, )) } @@ -2110,8 +2107,7 @@ mod tests { use dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; use dpp::identity::{IdentityPublicKey, SecurityLevel}; let data = dashcore::secp256k1::PublicKey::from_secret_key( - &dashcore::secp256k1::Secp256k1::new(), - &dashcore::secp256k1::SecretKey::from_slice(&[0x37u8; 32]).expect("secret"), + &dashcore::secp256k1::SecretKey::from_secret_bytes([0x37u8; 32]).expect("secret"), ) .serialize() .to_vec(); @@ -5575,8 +5571,7 @@ mod tests { key_type: KeyType::ECDSA_SECP256K1, read_only: false, data: dashcore::secp256k1::PublicKey::from_secret_key( - &dashcore::secp256k1::Secp256k1::new(), - &dashcore::secp256k1::SecretKey::from_slice(&[0x24u8; 32]).expect("secret"), + &dashcore::secp256k1::SecretKey::from_secret_bytes([0x24u8; 32]).expect("secret"), ) .serialize() .to_vec() @@ -5844,11 +5839,9 @@ mod tests { let provider = SeedCryptoProvider::from_seed(seed, Network::Testnet); // The contact's encryption keypair (the "sender" of the request). - let secp = dashcore::secp256k1::Secp256k1::new(); - let contact_secret = dashcore::secp256k1::SecretKey::from_slice(&[0x42u8; 32]) + let contact_secret = dashcore::secp256k1::SecretKey::from_secret_bytes([0x42u8; 32]) .expect("valid contact secret"); - let contact_public = - dashcore::secp256k1::PublicKey::from_secret_key(&secp, &contact_secret); + let contact_public = dashcore::secp256k1::PublicKey::from_secret_key(&contact_secret); // Our side, through the production provider. let ours = provider @@ -5932,7 +5925,6 @@ mod tests { let owner = Identifier::from([0xAA; 32]); let contact = Identifier::from([0xBB; 32]); - let secp = dashcore::secp256k1::Secp256k1::new(); let key_at = |id: u32, purpose: Purpose, byte: u8| { IdentityPublicKey::V0(IdentityPublicKeyV0 { id, @@ -5942,8 +5934,7 @@ mod tests { key_type: KeyType::ECDSA_SECP256K1, read_only: false, data: dashcore::secp256k1::PublicKey::from_secret_key( - &secp, - &dashcore::secp256k1::SecretKey::from_slice(&[byte; 32]).expect("secret"), + &dashcore::secp256k1::SecretKey::from_secret_bytes([byte; 32]).expect("secret"), ) .serialize() .to_vec() diff --git a/packages/rs-platform-wallet/src/wallet/masternode_withdrawal.rs b/packages/rs-platform-wallet/src/wallet/masternode_withdrawal.rs index 4ecf07f3b14..85e99301f1a 100644 --- a/packages/rs-platform-wallet/src/wallet/masternode_withdrawal.rs +++ b/packages/rs-platform-wallet/src/wallet/masternode_withdrawal.rs @@ -30,7 +30,7 @@ use std::fmt; use async_trait::async_trait; use dashcore::hashes::{hash160, sha256d, Hash}; use dashcore::secp256k1::ecdsa::{RecoverableSignature, RecoveryId}; -use dashcore::secp256k1::{Message, Secp256k1}; +use dashcore::secp256k1::Message; use dashcore::signer::CompactSignature; use dashcore::{Address as DashAddress, AddressType, Network, ScriptBuf}; use dpp::address_funds::AddressWitness; @@ -533,7 +533,7 @@ pub struct RawSecretCoreSigner { impl RawSecretCoreSigner { /// `secret` must be a valid secp256k1 scalar (32 bytes). pub fn from_bytes(secret: &[u8; 32]) -> Result { - let secret = dashcore::secp256k1::SecretKey::from_slice(secret).map_err(|_| { + let secret = dashcore::secp256k1::SecretKey::from_secret_bytes(*secret).map_err(|_| { PlatformWalletError::InvalidParameter("not a valid secp256k1 private key".to_string()) })?; Ok(Self { secret }) @@ -541,8 +541,7 @@ impl RawSecretCoreSigner { /// hash160 of this key's compressed public key. pub fn public_key_hash160(&self) -> [u8; 20] { - let secp = Secp256k1::signing_only(); - let public = dashcore::secp256k1::PublicKey::from_secret_key(&secp, &self.secret); + let public = dashcore::secp256k1::PublicKey::from_secret_key(&self.secret); hash160::Hash::hash(&public.serialize()).to_byte_array() } } @@ -573,11 +572,10 @@ impl CoreSigner for RawSecretCoreSigner { ), Self::Error, > { - let secp = Secp256k1::new(); let msg = Message::from_digest(sighash); Ok(( - secp.sign_ecdsa(&msg, &self.secret), - dashcore::secp256k1::PublicKey::from_secret_key(&secp, &self.secret), + self.secret.sign_ecdsa(msg), + dashcore::secp256k1::PublicKey::from_secret_key(&self.secret), )) } @@ -586,7 +584,6 @@ impl CoreSigner for RawSecretCoreSigner { _path: &DerivationPath, ) -> Result { Ok(dashcore::secp256k1::PublicKey::from_secret_key( - &Secp256k1::new(), &self.secret, )) } @@ -703,14 +700,14 @@ where ))); } - let secp = Secp256k1::verification_only(); let msg = Message::from_digest(digest); let compact = signature.serialize_compact(); let recoverable = (0..4i32) .filter_map(|id| RecoveryId::try_from(id).ok()) .filter_map(|recid| RecoverableSignature::from_compact(&compact, recid).ok()) .find(|candidate| { - secp.recover_ecdsa(&msg, candidate) + candidate + .recover_ecdsa(msg) .is_ok_and(|recovered| recovered == public_key) }) .ok_or_else(|| { @@ -763,22 +760,21 @@ mod tests { _path: &DerivationPath, sighash: [u8; 32], ) -> Result<(dashcore::secp256k1::ecdsa::Signature, PublicKey), Self::Error> { - let secp = Secp256k1::new(); let msg = Message::from_digest(sighash); Ok(( - secp.sign_ecdsa(&msg, &self.secret), - PublicKey::from_secret_key(&secp, &self.secret), + self.secret.sign_ecdsa(msg), + PublicKey::from_secret_key(&self.secret), )) } async fn public_key(&self, _path: &DerivationPath) -> Result { - Ok(PublicKey::from_secret_key(&Secp256k1::new(), &self.secret)) + Ok(PublicKey::from_secret_key(&self.secret)) } } fn fixture() -> (FixedKeySigner, [u8; 20], IdentityPublicKey) { - let secret = SecretKey::from_slice(&[0x42u8; 32]).expect("valid scalar"); - let pubkey = PublicKey::from_secret_key(&Secp256k1::new(), &secret); + let secret = SecretKey::from_secret_bytes([0x42u8; 32]).expect("valid scalar"); + let pubkey = PublicKey::from_secret_key(&secret); let hash: [u8; 20] = hash160::Hash::hash(&pubkey.serialize()).to_byte_array(); let key = IdentityPublicKey::V0(IdentityPublicKeyV0 { id: 0, diff --git a/packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs b/packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs index 6eb140ab58b..d522927468f 100644 --- a/packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs +++ b/packages/rs-platform-wallet/src/wallet/platform_addresses/provider.rs @@ -1142,7 +1142,6 @@ impl PlatformPaymentAddressProvider { #[cfg(test)] mod tests { use super::*; - use dashcore::secp256k1::Secp256k1; use key_wallet::bip32::ExtendedPrivKey; use key_wallet::Network; use key_wallet_manager::WalletManager; @@ -1151,10 +1150,9 @@ mod tests { const ACCOUNT: u32 = 0; fn test_xpub() -> ExtendedPubKey { - let secp = Secp256k1::new(); let seed = [42u8; 32]; let xprv = ExtendedPrivKey::new_master(Network::Testnet, &seed).expect("master xprv"); - ExtendedPubKey::from_priv(&secp, &xprv) + ExtendedPubKey::from_priv(&xprv) } fn p2pkh(byte: u8) -> PlatformP2PKHAddress { diff --git a/packages/rs-platform-wallet/src/wallet/platform_wallet_traits.rs b/packages/rs-platform-wallet/src/wallet/platform_wallet_traits.rs index 02d052359de..653465ca35e 100644 --- a/packages/rs-platform-wallet/src/wallet/platform_wallet_traits.rs +++ b/packages/rs-platform-wallet/src/wallet/platform_wallet_traits.rs @@ -136,6 +136,10 @@ impl WalletInfoInterface for PlatformWalletInfo { self.core_wallet.accounts() } + fn unrecorded_spend_heights(&self, tx: &Transaction) -> BTreeSet { + self.core_wallet.unrecorded_spend_heights(tx) + } + fn immature_transactions(&self) -> Vec { self.core_wallet.immature_transactions() } diff --git a/packages/rs-platform-wallet/src/wallet/provider_ecdsa_key_tests.rs b/packages/rs-platform-wallet/src/wallet/provider_ecdsa_key_tests.rs index 2e38a340120..6cd3bd07510 100644 --- a/packages/rs-platform-wallet/src/wallet/provider_ecdsa_key_tests.rs +++ b/packages/rs-platform-wallet/src/wallet/provider_ecdsa_key_tests.rs @@ -100,7 +100,6 @@ async fn private_reveal_is_internally_consistent() { let wallet = platform_wallet().await; let network = wallet_network(&wallet).await; - let secp = dashcore::key::Secp256k1::new(); for kind in [ProviderKeyKind::Owner, ProviderKeyKind::Voting] { for index in [0u32, 1, 19] { @@ -115,21 +114,21 @@ async fn private_reveal_is_internally_consistent() { // WIF and raw scalar must be the same key. let from_wif = PrivateKey::from_wif(wif).expect("valid WIF"); assert_eq!( - from_wif.inner.secret_bytes().to_vec(), + from_wif.inner.to_secret_bytes().to_vec(), **scalar, "{kind:?}#{index}: WIF and raw scalar disagree" ); // The reported public key must be this private key's. assert_eq!( - from_wif.public_key(&secp).to_bytes(), + from_wif.public_key().to_bytes(), derived.public_key_bytes, "{kind:?}#{index}: public key does not belong to the returned private key" ); // ...and the reported address must be that public key's P2PKH on // this wallet's own network, not a hardcoded chain. - let expected = dashcore::Address::p2pkh(&from_wif.public_key(&secp), network); + let expected = dashcore::Address::p2pkh(&from_wif.public_key(), network); assert_eq!( address, &expected.to_string(), diff --git a/packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs b/packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs index 7568ab06cfe..47c3ad04b51 100644 --- a/packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs +++ b/packages/rs-platform-wallet/src/wallet/provider_key_at_index.rs @@ -73,6 +73,8 @@ //! The seed and any returned scalar are wrapped in [`Zeroizing`] so they //! are scrubbed when dropped. +use dashcore::bls_sig_utils::BlsScheme; +use dashcore::eddsa::{EddsaPkBytes, EddsaPkHash, EddsaSecretKey}; use key_wallet::account::derivation::AccountDerivation; use key_wallet::account::{AccountType, BLSAccount, EdDSAAccount}; use key_wallet::bip32::{ChildNumber, ExtendedPrivKey}; @@ -154,12 +156,12 @@ pub fn derive_platform_node_public_keys( "failed to derive Ed25519 platform-node key at index {index}: {e}" )) })?; - let public_key: [u8; 32] = signing_key.verifying_key().to_bytes(); + let public_key: [u8; 32] = EddsaSecretKey::from(&signing_key).public_key().to_bytes(); // The 20-byte platform node id = SHA256(ed25519 pubkey)[..20] — the // Tenderdash/CometBFT convention (rust-dashcore #884) that a ProRegTx // `platform_node_id` field carries, NOT a hash160. let node_id: [u8; 20] = - dashcore::PlatformNodeId::from_ed25519_public_key(&public_key).to_byte_array(); + EddsaPkHash::from(EddsaPkBytes::from_bytes(public_key)).to_canonical_bytes(); out.push(ProviderPlatformNodePubKey { index, public_key, @@ -376,14 +378,20 @@ fn checked_operator_private_bytes_at( "failed to derive BLS operator key at index {index}: {e}" )) })?; - if secret.public_key().to_bytes().as_slice() != xpub_modern { + if secret + .as_scheme(BlsScheme::Modern) + .public_key() + .map_err(|e| PlatformWalletError::KeyDerivation(e.to_string()))? + .as_bytes() + != xpub_modern + { return Err(PlatformWalletError::KeyDerivation(format!( "BLS operator key at index {index}: seed-derived public key does not match the \ account xpub derivation — the wallet seed and its stored operator account xpub \ disagree; refusing to return a mismatched key" ))); } - Ok(include_private.then(|| Zeroizing::new(secret.to_be_bytes().to_vec()))) + Ok(include_private.then(|| Zeroizing::new(secret.to_bytes().to_vec()))) } impl PlatformWallet { @@ -496,7 +504,11 @@ impl PlatformWallet { let public_key_bytes = xpub.to_bytes().to_vec(); // Same G1 point in Dash legacy serialization (key-wallet // #879) — for the "BLS Public Key (Legacy)" display row. - let legacy_public_key_bytes = Some(xpub.to_bytes_legacy().to_vec()); + let legacy_public_key_bytes = Some( + xpub.to_bytes_legacy() + .map_err(|e| PlatformWalletError::KeyDerivation(e.to_string()))? + .to_vec(), + ); let private_key = match &seed { // Private reveal: the operator secret scalar from the @@ -555,15 +567,16 @@ impl PlatformWallet { "failed to derive Ed25519 platform-node key at index {index}: {e}" )) })?; - let verifying_bytes: [u8; 32] = signing_key.verifying_key().to_bytes(); + let verifying_bytes: [u8; 32] = + EddsaSecretKey::from(&signing_key).public_key().to_bytes(); let public_key_bytes = verifying_bytes.to_vec(); // The 20-byte platform node id = SHA256(ed25519 pubkey)[..20] // — the Tenderdash/CometBFT convention (rust-dashcore #884) // the ProRegTx `platform_node_id` field carries, NOT a hash160. let node_id: [u8; 20] = - dashcore::PlatformNodeId::from_ed25519_public_key(&verifying_bytes) - .to_byte_array(); + EddsaPkHash::from(EddsaPkBytes::from_bytes(verifying_bytes)) + .to_canonical_bytes(); let private_key = include_private.then(|| Zeroizing::new(signing_key.to_bytes().to_vec())); @@ -635,7 +648,6 @@ impl PlatformWallet { "failed to build master xpriv: {e}" )) })?; - let secp = dashcore::key::Secp256k1::new(); // The ACCOUNT's own path, not `account_type.derivation_path(network)`. // The public side above comes off `account.account_xpub`, built from // this path using the ACCOUNT's network; resolving it again from the @@ -654,11 +666,11 @@ impl PlatformWallet { )) })?; let mut child_xpriv = master - .derive_priv(&secp, &account_path) + .derive_priv(&account_path) .and_then(|acct| { let child_path: key_wallet::bip32::DerivationPath = vec![child].into(); - acct.derive_priv(&secp, &child_path) + acct.derive_priv(&child_path) }) .map_err(|e| { PlatformWalletError::KeyDerivation(format!( @@ -666,7 +678,7 @@ impl PlatformWallet { )) })?; let mut private = child_xpriv.to_priv(); - let derived_public = private.public_key(&secp); + let derived_public = private.public_key(); // Produce every output while the scalar is still live, // then erase it. `dashcore::PrivateKey` is `Copy` and @@ -677,7 +689,7 @@ impl PlatformWallet { // the `PrivateKey` itself. let outputs = if derived_public == public_key { Some(( - Zeroizing::new(private.inner.secret_bytes().to_vec()), + Zeroizing::new(private.inner.to_secret_bytes().to_vec()), Zeroizing::new(private.to_wif()), )) } else { @@ -762,7 +774,7 @@ mod tests { for (i, k) in keys.iter().enumerate() { assert_eq!(k.index, i as u32, "index ordering"); let expected_node_id: [u8; 20] = - dashcore::PlatformNodeId::from_ed25519_public_key(&k.public_key).to_byte_array(); + EddsaPkHash::from(EddsaPkBytes::from_bytes(k.public_key)).to_canonical_bytes(); assert_eq!( k.node_id, expected_node_id, "node_id must be SHA256(ed25519 pubkey)[..20] at index {i}" @@ -776,7 +788,7 @@ mod tests { .expect("platform_node_key_at"); assert_eq!( k.public_key, - via_api.verifying_key().to_bytes(), + EddsaSecretKey::from(&via_api).public_key().to_bytes(), "snapshot pubkey must equal platform_node_key_at at {i}" ); } @@ -801,7 +813,7 @@ mod tests { // The snapshot's index-0 public key corresponds to that golden secret. assert_eq!( keys[0].public_key, - sk0.verifying_key().to_bytes(), + EddsaSecretKey::from(&sk0).public_key().to_bytes(), "snapshot index-0 pubkey must match the golden secret's public key" ); // Golden pubkey + Tenderdash node-id (SHA256[..20]) for mainnet @@ -840,7 +852,7 @@ mod tests { // account and on watch-only accounts alike). let xpub0 = account.operator_public_key_at(0).expect("operator public"); assert_eq!( - hex::encode(xpub0.to_bytes_legacy()), + hex::encode(xpub0.to_bytes_legacy().expect("legacy public key")), "078cad04aae29eb76171937eb7101452b401b026efbc27db840f130374e6a9ec8443d917277f8921e0ba6678a7709875", "operator key0 legacy pubkey golden (dashbls reference)" ); @@ -854,14 +866,17 @@ mod tests { let sk0 = BLSAccount::operator_private_key_at(&seed, Network::Mainnet, 0) .expect("operator private"); assert_eq!( - hex::encode(sk0.to_be_bytes()), + hex::encode(sk0.to_bytes()), "11122e1ad656d0610ce0f80d40da874d67ea656a3e66ed371c915ec3a488a43a", "operator key0 secret golden (dashbls reference)" ); // The always-on cross-check the adapter runs: the seed-derived // secret's public key must equal the account-xpub-derived public. assert_eq!( - sk0.public_key().to_bytes(), + sk0.as_scheme(BlsScheme::Modern) + .public_key() + .expect("BLS public key") + .to_bytes(), xpub0.public_key.to_bytes(), "seed-derived operator pubkey must equal the account-xpub derivation" ); @@ -877,19 +892,23 @@ mod tests { .operator_public_key_at(0) .expect("operator public"); assert_eq!( - hex::encode(xpub0_t.to_bytes_legacy()), + hex::encode(xpub0_t.to_bytes_legacy().expect("legacy public key")), "09d8beabae708de1638487f1aff44b38e8c07d9b09f22d76329d6c8ec01e2ad4d030b660bca40ddbd222373a72c5bcef", "testnet operator key0 legacy pubkey golden (dashbls reference)" ); let sk0_t = BLSAccount::operator_private_key_at(&seed_t, Network::Testnet, 0) .expect("operator private"); assert_eq!( - hex::encode(sk0_t.to_be_bytes()), + hex::encode(sk0_t.to_bytes()), "3346dfd71627f9f31cad3ee66fe7b673c32cb077b2eb38c621d7e61c30e46dbd", "testnet operator key0 secret golden (dashbls reference)" ); assert_eq!( - sk0_t.public_key().to_bytes(), + sk0_t + .as_scheme(BlsScheme::Modern) + .public_key() + .expect("BLS public key") + .to_bytes(), xpub0_t.public_key.to_bytes(), "testnet seed-derived operator pubkey must equal the account-xpub derivation" ); @@ -1030,7 +1049,7 @@ mod tests { // scan recomputing SHA256[..20] from the pubkey maps back to // this index. let expected_node_id = - dashcore::PlatformNodeId::from_ed25519_public_key(&k.public_key).to_byte_array(); + EddsaPkHash::from(EddsaPkBytes::from_bytes(k.public_key)).to_canonical_bytes(); assert_eq!( k.node_id, expected_node_id, "node id must be the Tenderdash SHA256[..20]" @@ -1096,7 +1115,6 @@ mod tests { let wallet = seed_bearing_wallet(network); let seed = wallet.wallet_seed_bytes().expect("resident seed"); let master = ExtendedPrivKey::new_master(network, &seed).expect("master xpriv"); - let secp = dashcore::key::Secp256k1::new(); for (kind, family) in [ (ProviderKeyKind::Owner, "2'"), @@ -1115,11 +1133,11 @@ mod tests { let path = DerivationPath::from_str(&format!("m/9'/{coin}/3'/{family}/{index}")) .expect("explicit DIP-3 path"); - let expected = master.derive_priv(&secp, &path).expect("path derivation"); + let expected = master.derive_priv(&path).expect("path derivation"); assert_eq!( - derived.inner.secret_bytes(), - expected.private_key.secret_bytes(), + derived.inner.to_secret_bytes(), + expected.private_key.to_secret_bytes(), "{kind:?} key at index {index} on {network:?} must come from \ m/9'/{coin}/3'/{family}/{index}" ); @@ -1150,14 +1168,13 @@ mod tests { let master = ExtendedPrivKey::new_master(Network::Mainnet, &seed).expect("master"); let doubled = master .derive_priv( - &dashcore::key::Secp256k1::new(), &DerivationPath::from_str("m/9'/5'/3'/1'/9'/5'/3'/1'/19").expect("doubled path"), ) .expect("doubled derivation"); assert_ne!( - derived.inner.secret_bytes(), - doubled.private_key.secret_bytes(), + derived.inner.to_secret_bytes(), + doubled.private_key.to_secret_bytes(), "the account derivation path is being applied twice again" ); } @@ -1181,7 +1198,6 @@ mod tests { let wallet = seed_bearing_wallet(Network::Mainnet); let seed = wallet.wallet_seed_bytes().expect("resident seed"); - let secp = dashcore::key::Secp256k1::new(); let master = ExtendedPrivKey::new_master(Network::Mainnet, &seed).expect("master"); for (kind, family) in [ @@ -1216,22 +1232,21 @@ mod tests { let child = ChildNumber::from_normal_idx(19).expect("child index"); let child_path: DerivationPath = vec![child].into(); let derived = master - .derive_priv(&secp, &account_path) - .and_then(|acct| acct.derive_priv(&secp, &child_path)) + .derive_priv(&account_path) + .and_then(|acct| acct.derive_priv(&child_path)) .expect("gate-free derivation") .to_priv(); let expected = master .derive_priv( - &secp, &DerivationPath::from_str(&format!("m/9'/5'/3'/{family}/19")) .expect("explicit path"), ) .expect("path derivation"); assert_eq!( - derived.inner.secret_bytes(), - expected.private_key.secret_bytes(), + derived.inner.to_secret_bytes(), + expected.private_key.to_secret_bytes(), "{kind:?} watch-only derivation must match m/9'/5'/3'/{family}/19" ); } diff --git a/packages/rs-scripts/src/bin/register_identity.rs b/packages/rs-scripts/src/bin/register_identity.rs index f8c5f873b34..5caa3060869 100644 --- a/packages/rs-scripts/src/bin/register_identity.rs +++ b/packages/rs-scripts/src/bin/register_identity.rs @@ -88,7 +88,6 @@ use dash_sdk::platform::types::epoch::Epoch; use dash_sdk::{Sdk, SdkBuilder}; use dpp::balances::credits::CREDITS_PER_DUFF; use dpp::dashcore::consensus::encode::{deserialize, serialize}; -use dpp::dashcore::secp256k1::Secp256k1; use dpp::dashcore::transaction::special_transaction::asset_lock::AssetLockPayload; use dpp::dashcore::transaction::special_transaction::TransactionPayload; use dpp::dashcore::{ @@ -515,11 +514,10 @@ async fn run() -> Result<(), String> { } else { // FRESH: generate a one-time key, build + Core-sign + broadcast the // asset lock, capturing all recovery credentials BEFORE broadcast. - let secp = Secp256k1::new(); - let mut secp_rng = dpp::dashcore::secp256k1::rand::thread_rng(); + let mut secp_rng = dpp::dashcore::secp256k1::rand::rng(); let one_time_secret = dpp::dashcore::secp256k1::SecretKey::new(&mut secp_rng); let one_time_private_key = PrivateKey::new(one_time_secret, network); - let one_time_public_key = one_time_private_key.public_key(&secp); + let one_time_public_key = one_time_private_key.public_key(); let one_time_key_hash = one_time_public_key.pubkey_hash(); let one_time_address = Address::p2pkh(&one_time_public_key, network); diff --git a/packages/rs-sdk-ffi/src/address/transitions/transfer.rs b/packages/rs-sdk-ffi/src/address/transitions/transfer.rs index 7e8523710eb..a3ef56455f6 100644 --- a/packages/rs-sdk-ffi/src/address/transitions/transfer.rs +++ b/packages/rs-sdk-ffi/src/address/transitions/transfer.rs @@ -59,8 +59,9 @@ impl Signer for AddressSigner { })?; // Sign the data using dashcore signer - let signature = dash_sdk::dpp::dashcore::signer::sign(data, private_key.inner.as_ref()) - .map_err(|e| ProtocolError::Generic(format!("Signing failed: {}", e)))?; + let signature = + dash_sdk::dpp::dashcore::signer::sign(data, private_key.inner.as_secret_bytes()) + .map_err(|e| ProtocolError::Generic(format!("Signing failed: {}", e)))?; Ok(BinaryData::new(signature.to_vec())) } @@ -83,8 +84,9 @@ impl Signer for AddressSigner { })?; // Sign the data - let signature = dash_sdk::dpp::dashcore::signer::sign(data, private_key.inner.as_ref()) - .map_err(|e| ProtocolError::Generic(format!("Signing failed: {}", e)))?; + let signature = + dash_sdk::dpp::dashcore::signer::sign(data, private_key.inner.as_secret_bytes()) + .map_err(|e| ProtocolError::Generic(format!("Signing failed: {}", e)))?; // Create P2PKH witness (most common for single key) Ok(AddressWitness::P2pkh { @@ -236,7 +238,10 @@ unsafe fn dash_sdk_address_transfer_funds_inner( // Parse private key (32 bytes) let pk_bytes = std::slice::from_raw_parts(input.private_key, 32); - let secret_key = match SecretKey::from_slice(pk_bytes) { + let secret_key = match <[u8; 32]>::try_from(pk_bytes) + .map_err(|_| dash_sdk::dpp::dashcore::secp256k1::Error::InvalidSecretKey) + .and_then(SecretKey::from_secret_bytes) + { Ok(sk) => sk, Err(e) => { return DashSDKResult::error(DashSDKError::new( diff --git a/packages/rs-sdk-ffi/src/address/transitions/withdraw.rs b/packages/rs-sdk-ffi/src/address/transitions/withdraw.rs index 54f7d590fb3..db685b4bf0b 100644 --- a/packages/rs-sdk-ffi/src/address/transitions/withdraw.rs +++ b/packages/rs-sdk-ffi/src/address/transitions/withdraw.rs @@ -210,7 +210,10 @@ unsafe fn dash_sdk_address_withdraw_funds_inner( // Parse private key (32 bytes) let pk_bytes = std::slice::from_raw_parts(input.private_key, 32); - let secret_key = match SecretKey::from_slice(pk_bytes) { + let secret_key = match <[u8; 32]>::try_from(pk_bytes) + .map_err(|_| dash_sdk::dpp::dashcore::secp256k1::Error::InvalidSecretKey) + .and_then(SecretKey::from_secret_bytes) + { Ok(sk) => sk, Err(e) => { return DashSDKResult::error(DashSDKError::new( diff --git a/packages/rs-sdk-ffi/src/contested_resource/transitions/cast_vote.rs b/packages/rs-sdk-ffi/src/contested_resource/transitions/cast_vote.rs index 6a8570f0309..2451b3ce171 100644 --- a/packages/rs-sdk-ffi/src/contested_resource/transitions/cast_vote.rs +++ b/packages/rs-sdk-ffi/src/contested_resource/transitions/cast_vote.rs @@ -61,7 +61,7 @@ use crate::sdk::SDKWrapper; use crate::types::{FFINetwork, Network, SDKHandle}; use crate::{DashSDKResult, FFIError}; use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; -use dash_sdk::dpp::dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use dash_sdk::dpp::dashcore::secp256k1::{PublicKey, SecretKey}; use dash_sdk::dpp::dashcore::ProTxHash; use dash_sdk::dpp::platform_value::{Identifier, Value}; use dash_sdk::dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice; @@ -341,10 +341,9 @@ unsafe fn cast_vote_inner( // `masternode_voting_key.public_key_hash()` to derive the voter // identifier, and `SingleKeySigner::can_sign_with` recomputes the same // hash160 from the private key, so the two agree by construction. - let secp = Secp256k1::new(); - let secret_key = SecretKey::from_byte_array(&key_array) + let secret_key = SecretKey::from_secret_bytes(*key_array) .map_err(|e| invalid(&format!("Invalid voting private key: {}", e)))?; - let public_key = PublicKey::from_secret_key(&secp, &secret_key); + let public_key = PublicKey::from_secret_key(&secret_key); let voting_address = hash160::Hash::hash(&public_key.serialize()).to_byte_array(); // ---- Broadcast, then diagnose only on failure --------------------------- diff --git a/packages/rs-sdk-ffi/src/identity/create_from_addresses.rs b/packages/rs-sdk-ffi/src/identity/create_from_addresses.rs index c90e2e3dae3..cc32e028811 100644 --- a/packages/rs-sdk-ffi/src/identity/create_from_addresses.rs +++ b/packages/rs-sdk-ffi/src/identity/create_from_addresses.rs @@ -170,7 +170,10 @@ unsafe fn dash_sdk_identity_create_from_addresses_inner( // Parse private key (32 bytes) let pk_bytes = std::slice::from_raw_parts(input.private_key, 32); - let secret_key = match SecretKey::from_slice(pk_bytes) { + let secret_key = match <[u8; 32]>::try_from(pk_bytes) + .map_err(|_| dash_sdk::dpp::dashcore::secp256k1::Error::InvalidSecretKey) + .and_then(SecretKey::from_secret_bytes) + { Ok(sk) => sk, Err(e) => { return DashSDKResult::error(DashSDKError::new( diff --git a/packages/rs-sdk-ffi/src/identity/helpers.rs b/packages/rs-sdk-ffi/src/identity/helpers.rs index 0e106bfd9b6..bb10745d4d0 100644 --- a/packages/rs-sdk-ffi/src/identity/helpers.rs +++ b/packages/rs-sdk-ffi/src/identity/helpers.rs @@ -89,7 +89,7 @@ pub unsafe fn parse_private_key( private_key_bytes: *const [u8; 32], ) -> Result { let key_bytes = *private_key_bytes; - let secret_key = dashcore::secp256k1::SecretKey::from_byte_array(&key_bytes) + let secret_key = dashcore::secp256k1::SecretKey::from_secret_bytes(key_bytes) .map_err(|e| FFIError::InternalError(format!("Invalid private key: {}", e)))?; Ok(PrivateKey::new(secret_key, Network::Mainnet)) } diff --git a/packages/rs-sdk-ffi/src/identity/top_up_from_addresses.rs b/packages/rs-sdk-ffi/src/identity/top_up_from_addresses.rs index c40346e102b..9cc74ccd280 100644 --- a/packages/rs-sdk-ffi/src/identity/top_up_from_addresses.rs +++ b/packages/rs-sdk-ffi/src/identity/top_up_from_addresses.rs @@ -151,7 +151,10 @@ unsafe fn dash_sdk_identity_top_up_from_addresses_inner( // Parse private key (32 bytes) let pk_bytes = std::slice::from_raw_parts(input.private_key, 32); - let secret_key = match SecretKey::from_slice(pk_bytes) { + let secret_key = match <[u8; 32]>::try_from(pk_bytes) + .map_err(|_| dash_sdk::dpp::dashcore::secp256k1::Error::InvalidSecretKey) + .and_then(SecretKey::from_secret_bytes) + { Ok(sk) => sk, Err(e) => { return DashSDKResult::error(DashSDKError::new( diff --git a/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs b/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs index e6a9367c4ab..c4be3c24dc6 100644 --- a/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs +++ b/packages/rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs @@ -52,7 +52,7 @@ //! seed, and the final derived 32-byte scalar. //! - **The `WipingSecretKey` RAII guard** scrubs the raw //! [`secp256k1::SecretKey`] copies at the two sign sites, where the -//! scalar comes back out of `SecretKey::from_slice`. `SecretKey` is an +//! scalar comes back out of `SecretKey::from_secret_bytes`. `SecretKey` is an //! upstream secp256k1 type with no `Zeroize` impl (only //! `non_secure_erase()`), so it can't ride a `Zeroizing` wrapper; the //! guard wipes it on every exit path — normal return, `?`-early-return, @@ -67,7 +67,7 @@ use std::os::raw::c_char; use async_trait::async_trait; use key_wallet::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey}; -use key_wallet::dashcore::secp256k1::{self, Secp256k1}; +use key_wallet::dashcore::secp256k1; use key_wallet::dip9::{ DASHPAY_CONTACT_INFO_ENC_TO_USER_ID_CHILD, DASHPAY_CONTACT_INFO_PRIVATE_DATA_CHILD, FEATURE_PURPOSE, FEATURE_PURPOSE_DASHPAY_AUTO_ACCEPT, FEATURE_PURPOSE_IDENTITIES, @@ -330,11 +330,10 @@ impl MnemonicResolverCoreSigner { let seed: Zeroizing<[u8; 64]> = Zeroizing::new(mnemonic.to_seed("")); drop(mnemonic); - let secp = Secp256k1::new(); let master = ExtendedPrivKey::new_master(self.network, seed.as_ref()) .map_err(|e| MnemonicResolverSignerError::DerivationFailed(format!("master: {e}")))?; let derived = master - .derive_priv(&secp, path) + .derive_priv(path) .map_err(|e| MnemonicResolverSignerError::DerivationFailed(format!("path: {e}")))?; Ok(extract(&derived)) @@ -350,10 +349,10 @@ impl MnemonicResolverCoreSigner { &self, path: &DerivationPath, ) -> Result, MnemonicResolverSignerError> { - // `secret_bytes()` copies the scalar out of the borrowed key; the + // `to_secret_bytes()` copies the scalar out of the borrowed key; the // `ExtendedPrivKey` itself never leaves `resolve_and_derive`. self.resolve_and_derive(path, |derived| { - Zeroizing::new(derived.private_key.secret_bytes()) + Zeroizing::new(derived.private_key.to_secret_bytes()) }) } @@ -607,7 +606,7 @@ pub struct ContactInfoOpened { /// /// `SecretKey` is an upstream secp256k1 type with no `Zeroize` impl (only /// `non_secure_erase()`), so it can't ride a `Zeroizing` wrapper. Wrapping the -/// `SecretKey::from_slice` copy here wipes it on every exit path — normal +/// `SecretKey::from_secret_bytes` copy here wipes it on every exit path — normal /// return, `?`-early-return, and panic-unwind — closing the leak window a bare /// inline `non_secure_erase()` would leave open between construction and the /// manual scrub. This is the one key intermediate that upstream key-wallet's @@ -637,31 +636,29 @@ impl Signer for MnemonicResolverCoreSigner { sighash: [u8; 32], ) -> Result<(secp256k1::ecdsa::Signature, secp256k1::PublicKey), Self::Error> { let secret_bytes = self.derive_priv(path)?; - let secp = Secp256k1::new(); - // `SecretKey::from_slice` validates the 32-byte scalar is a + // `SecretKey::from_secret_bytes` validates the 32-byte scalar is a // legitimate field element. The `WipingSecretKey` guard scrubs this // separate copy on every exit path, including a panic between here and // the return — `Zeroizing<[u8;32]>` already covers `secret_bytes`. let secret = WipingSecretKey( - secp256k1::SecretKey::from_slice(secret_bytes.as_ref()) + secp256k1::SecretKey::from_secret_bytes(*secret_bytes) .map_err(|e| MnemonicResolverSignerError::InvalidScalar(e.to_string()))?, ); let msg = secp256k1::Message::from_digest(sighash); - let signature = secp.sign_ecdsa(&msg, &secret.0); - let pubkey = secp256k1::PublicKey::from_secret_key(&secp, &secret.0); + let signature = secret.0.sign_ecdsa(msg); + let pubkey = secp256k1::PublicKey::from_secret_key(&secret.0); Ok((signature, pubkey)) } async fn public_key(&self, path: &DerivationPath) -> Result { let secret_bytes = self.derive_priv(path)?; - let secp = Secp256k1::new(); - // `WipingSecretKey` scrubs this `from_slice` copy on every exit path, + // `WipingSecretKey` scrubs this `from_secret_bytes` copy on every exit path, // including panic-unwind — `Zeroizing<[u8;32]>` covers `secret_bytes`. let secret = WipingSecretKey( - secp256k1::SecretKey::from_slice(secret_bytes.as_ref()) + secp256k1::SecretKey::from_secret_bytes(*secret_bytes) .map_err(|e| MnemonicResolverSignerError::InvalidScalar(e.to_string()))?, ); - let pubkey = secp256k1::PublicKey::from_secret_key(&secp, &secret.0); + let pubkey = secp256k1::PublicKey::from_secret_key(&secret.0); Ok(pubkey) } } @@ -680,10 +677,9 @@ impl ExtendedPubKeySigner for MnemonicResolverCoreSigner { &self, path: &DerivationPath, ) -> Result { - let secp = Secp256k1::new(); // `ExtendedPubKey` carries only public material (chain code + point); // the borrowed private key never leaves `resolve_and_derive`. - self.resolve_and_derive(path, |derived| ExtendedPubKey::from_priv(&secp, derived)) + self.resolve_and_derive(path, ExtendedPubKey::from_priv) } } @@ -750,9 +746,8 @@ mod tests { .await .expect("signing succeeds"); - let secp = Secp256k1::new(); let msg = secp256k1::Message::from_digest(sighash); - secp.verify_ecdsa(&msg, &sig, &pk) + sig.verify(msg, &pk) .expect("signature must verify against returned pubkey"); unsafe { dash_sdk_mnemonic_resolver_destroy(resolver) }; @@ -899,12 +894,11 @@ mod tests { // Independently derive the expected xpub straight from the known // BIP-39 vector — same network + path, no resolver in the loop. - let secp = Secp256k1::new(); let mnemonic = parse_mnemonic_any_language(ENGLISH_PHRASE).expect("valid phrase"); let master = ExtendedPrivKey::new_master(Network::Testnet, &mnemonic.to_seed("")) .expect("master derivation"); - let derived = master.derive_priv(&secp, &path).expect("path derivation"); - let expected = ExtendedPubKey::from_priv(&secp, &derived); + let derived = master.derive_priv(&path).expect("path derivation"); + let expected = ExtendedPubKey::from_priv(&derived); // Field-level checks run first so a silently-dropped BIP-32 metadatum // fails here with a precise message — not just the public point. The @@ -1005,9 +999,9 @@ mod tests { let path = test_path(); // A fixed peer keypair (the contact's encryption key). - let secp = Secp256k1::new(); - let peer_sk = secp256k1::SecretKey::from_slice(&[0x42u8; 32]).expect("peer secret key"); - let peer_pk = secp256k1::PublicKey::from_secret_key(&secp, &peer_sk); + let peer_sk = + secp256k1::SecretKey::from_secret_bytes([0x42u8; 32]).expect("peer secret key"); + let peer_pk = secp256k1::PublicKey::from_secret_key(&peer_sk); // Old route: resident-seed wallet from the same mnemonic → derive the // scalar at `path` → ECDH through the single crypto source. @@ -1072,7 +1066,7 @@ mod tests { .derive_extended_private_key(&path) .expect("wallet derives the private key at path") .private_key - .secret_bytes(); + .to_secret_bytes(); let expected = platform_encryption::calculate_account_reference( &secret, &compact_xpub, @@ -1164,7 +1158,7 @@ mod tests { .derive_extended_private_key(&path) .expect("derive encToUserId key") .private_key - .secret_bytes() + .to_secret_bytes() }; let expected_enc = platform_encryption::encrypt_enc_to_user_id(&enc_key, &contact_id); assert_eq!( diff --git a/packages/rs-sdk-ffi/src/signer_simple.rs b/packages/rs-sdk-ffi/src/signer_simple.rs index e3b3d34e2ba..3ad020582d8 100644 --- a/packages/rs-sdk-ffi/src/signer_simple.rs +++ b/packages/rs-sdk-ffi/src/signer_simple.rs @@ -311,7 +311,6 @@ pub unsafe extern "C" fn dash_sdk_sign_with_mnemonic_and_path( out_signature_len: *mut usize, out_error: *mut u8, ) -> i32 { - use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; use dash_sdk::dpp::identity::KeyType; use key_wallet::bip32::{DerivationPath, ExtendedPrivKey}; use std::ffi::CStr; @@ -396,8 +395,7 @@ pub unsafe extern "C" fn dash_sdk_sign_with_mnemonic_and_path( Ok(m) => m, Err(_) => return fail(SIGN_WITH_MNEMONIC_ERR_DERIVATION), }; - let secp = Secp256k1::new(); - let derived = match master.derive_priv(&secp, &path) { + let derived = match master.derive_priv(&path) { Ok(d) => d, Err(_) => return fail(SIGN_WITH_MNEMONIC_ERR_DERIVATION), }; @@ -406,7 +404,7 @@ pub unsafe extern "C" fn dash_sdk_sign_with_mnemonic_and_path( // returns. `derived` self-wipes separately: `ExtendedPrivKey` // zeroizes on `Drop`. let secret_bytes: zeroize::Zeroizing<[u8; 32]> = - zeroize::Zeroizing::new(derived.private_key.secret_bytes()); + zeroize::Zeroizing::new(derived.private_key.to_secret_bytes()); // ---- Sign --------------------------------------------------------------- // `dashcore::signer::sign` returns a 65-byte compact recoverable diff --git a/packages/rs-sdk-ffi/src/system/queries/current_quorums_info.rs b/packages/rs-sdk-ffi/src/system/queries/current_quorums_info.rs index 6105efcd5bc..d5580ebce62 100644 --- a/packages/rs-sdk-ffi/src/system/queries/current_quorums_info.rs +++ b/packages/rs-sdk-ffi/src/system/queries/current_quorums_info.rs @@ -105,7 +105,7 @@ fn get_current_quorums_info(sdk_handle: *const SDKHandle) -> Result [u8; AES_CBC_IV_LENGTH] { let mut iv = [0u8; AES_CBC_IV_LENGTH]; - StdRng::from_entropy().fill_bytes(&mut iv); + StdRng::from_os_rng().fill_bytes(&mut iv); iv } @@ -425,8 +425,7 @@ fn select_declared_keys<'a>( let contract_id = document_type.data_contract_id(); let sender_requirements = key_requirements_naming(document_type, &declaration.sender_key); - let sender_public_key = - PublicKey::from_secret_key(&Secp256k1::signing_only(), sender_private_key); + let sender_public_key = PublicKey::from_secret_key(sender_private_key); let sender_public_key_bytes = sender_public_key.serialize(); let no_sender_key = |reason: String| EncryptedForError::NoSuitableKey { identity_id: sender.id(), diff --git a/packages/rs-sdk/src/platform/encrypted_for/tests.rs b/packages/rs-sdk/src/platform/encrypted_for/tests.rs index 5660550e359..8f88a83288b 100644 --- a/packages/rs-sdk/src/platform/encrypted_for/tests.rs +++ b/packages/rs-sdk/src/platform/encrypted_for/tests.rs @@ -34,8 +34,8 @@ const DASHPAY_VECTOR_HEX: &str = "5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a864d1b3807cf80 b97cd32a5e36309f3bf9535c519b1b32b2f206696ec6d0e244a2e182fceaa750"; fn key_pair(scalar: u8) -> (SecretKey, PublicKey) { - let secret_key = SecretKey::from_slice(&[scalar; 32]).expect("a valid scalar"); - let public_key = PublicKey::from_secret_key(&Secp256k1::signing_only(), &secret_key); + let secret_key = SecretKey::from_secret_bytes([scalar; 32]).expect("a valid scalar"); + let public_key = PublicKey::from_secret_key(&secret_key); (secret_key, public_key) } diff --git a/packages/rs-sdk/src/platform/moderation_charters/requests.rs b/packages/rs-sdk/src/platform/moderation_charters/requests.rs index a51d8402385..9af9df68a99 100644 --- a/packages/rs-sdk/src/platform/moderation_charters/requests.rs +++ b/packages/rs-sdk/src/platform/moderation_charters/requests.rs @@ -4,7 +4,7 @@ use crate::platform::encrypted_for::encrypt_property_for; use crate::platform::{DataContract, Document, Fetch, Identity}; use crate::{Error, Sdk}; use dpp::dashcore::secp256k1::rand::rngs::StdRng; -use dpp::dashcore::secp256k1::rand::SeedableRng; +use dpp::dashcore::secp256k1::rand::{Rng, SeedableRng}; use dpp::dashcore::secp256k1::SecretKey; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::document_type::methods::DocumentTypeBasicMethods; @@ -240,14 +240,14 @@ impl Sdk { } fn fresh_entropy() -> Bytes32 { - Bytes32::random_with_rng(&mut StdRng::from_entropy()) + Bytes32::new(StdRng::from_os_rng().random()) } #[cfg(test)] mod tests { use super::*; use crate::platform::encrypted_for::{decrypt_property, EncryptedPropertyEnvelope}; - use dpp::dashcore::secp256k1::{PublicKey, Secp256k1}; + use dpp::dashcore::secp256k1::PublicKey; use dpp::data_contract::document_type::property_constraints::DocumentSystemValues; use dpp::data_contract::validate_document::DataContractDocumentValidationMethodsV0; use dpp::identity::contract_bounds::ContractBounds; @@ -260,8 +260,8 @@ mod tests { use dpp::version::PlatformVersion; fn key_pair(scalar: u8) -> (SecretKey, PublicKey) { - let secret_key = SecretKey::from_slice(&[scalar; 32]).expect("a valid scalar"); - let public_key = PublicKey::from_secret_key(&Secp256k1::signing_only(), &secret_key); + let secret_key = SecretKey::from_secret_bytes([scalar; 32]).expect("a valid scalar"); + let public_key = PublicKey::from_secret_key(&secret_key); (secret_key, public_key) } diff --git a/packages/rs-sdk/src/platform/transition/broadcast_identity.rs b/packages/rs-sdk/src/platform/transition/broadcast_identity.rs index 3b07883f310..319fa8d5269 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast_identity.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast_identity.rs @@ -154,7 +154,7 @@ impl> IdentityCreateTransition::try_from_identity_with_signer_and_private_key( self, asset_lock_proof, - asset_lock_proof_private_key.inner.as_ref(), + asset_lock_proof_private_key.inner.as_secret_bytes(), signer, &NativeBlsModule, user_fee_increase, diff --git a/packages/rs-sdk/src/platform/transition/put_document.rs b/packages/rs-sdk/src/platform/transition/put_document.rs index e090797d275..171ae87faa9 100644 --- a/packages/rs-sdk/src/platform/transition/put_document.rs +++ b/packages/rs-sdk/src/platform/transition/put_document.rs @@ -162,9 +162,9 @@ impl> PutDocument for Document { (document, entropy) } None => { - let mut rng = StdRng::from_entropy(); + let mut rng = StdRng::from_os_rng(); let mut document = document; - let entropy = rng.gen::<[u8; 32]>(); + let entropy = rng.random::<[u8; 32]>(); document.set_id(Document::generate_document_id( &document_type.data_contract_id(), &document.owner_id(), diff --git a/packages/rs-sdk/src/platform/transition/top_up_address.rs b/packages/rs-sdk/src/platform/transition/top_up_address.rs index 3e7bfa58826..e8de2cc33c6 100644 --- a/packages/rs-sdk/src/platform/transition/top_up_address.rs +++ b/packages/rs-sdk/src/platform/transition/top_up_address.rs @@ -159,7 +159,7 @@ impl> TopUpAddress for AddressesWithBalances { let state_transition = create_address_funding_from_asset_lock_transition( asset_lock_proof, - asset_lock_private_key.inner.as_ref(), + asset_lock_private_key.inner.as_secret_bytes(), BTreeMap::new(), self.clone(), fee_strategy, diff --git a/packages/rs-sdk/src/platform/transition/top_up_identity.rs b/packages/rs-sdk/src/platform/transition/top_up_identity.rs index aa98bf00219..3962ff00fcf 100644 --- a/packages/rs-sdk/src/platform/transition/top_up_identity.rs +++ b/packages/rs-sdk/src/platform/transition/top_up_identity.rs @@ -81,7 +81,7 @@ impl TopUpIdentity for Identity { let state_transition = IdentityTopUpTransition::try_from_identity_with_private_key( self, asset_lock_proof, - asset_lock_proof_private_key.inner.as_ref(), + asset_lock_proof_private_key.inner.as_secret_bytes(), user_fee_increase, sdk.version(), None, diff --git a/packages/rs-unified-sdk-jni/src/tx_decode.rs b/packages/rs-unified-sdk-jni/src/tx_decode.rs index 015a88a8bc6..f5b5772e440 100644 --- a/packages/rs-unified-sdk-jni/src/tx_decode.rs +++ b/packages/rs-unified-sdk-jni/src/tx_decode.rs @@ -210,7 +210,7 @@ mod tests { use super::*; use dashcore::consensus::serialize; use dashcore::hashes::Hash; - use dashcore::secp256k1::{Secp256k1, SecretKey}; + use dashcore::secp256k1::SecretKey; use dashcore::{ Address, Network, OutPoint, PublicKey, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness, }; @@ -247,9 +247,8 @@ mod tests { } fn test_pubkey() -> PublicKey { - let secp = Secp256k1::new(); - let sk = SecretKey::from_slice(&[0x42u8; 32]).expect("valid secret key"); - PublicKey::new(sk.public_key(&secp)) + let sk = SecretKey::from_secret_bytes([0x42u8; 32]).expect("valid secret key"); + PublicKey::new(sk.public_key()) } /// The same deterministic fixture as key-wallet-ffi's diff --git a/packages/simple-signer/Cargo.toml b/packages/simple-signer/Cargo.toml index 089fae064ff..d09a1910d2a 100644 --- a/packages/simple-signer/Cargo.toml +++ b/packages/simple-signer/Cargo.toml @@ -23,6 +23,9 @@ async-trait = { version = "0.1.79" } bincode = { workspace = true, features = ["serde"] } base64 = { version = "0.22.1" } hex = { version = "0.4.3" } +# rand 0.8 `RngCore`, the RNG type strategy-tests/drive-abci pass in; the +# secp256k1 re-export is rand 0.9. +rand = { version = "0.8", default-features = false } tracing = "0.1.41" zeroize = "1" diff --git a/packages/simple-signer/src/signer.rs b/packages/simple-signer/src/signer.rs index c7fc229e551..9a760c0db5e 100644 --- a/packages/simple-signer/src/signer.rs +++ b/packages/simple-signer/src/signer.rs @@ -3,9 +3,10 @@ use base64::prelude::BASE64_STANDARD; use base64::Engine; use dpp::address_funds::{AddressWitness, PlatformAddress}; use dpp::bincode::{Decode, Encode}; -use dpp::bls_signatures::{Bls12381G2Impl, SignatureSchemes}; -use dpp::dashcore::secp256k1::rand::{RngCore, SeedableRng}; -use dpp::dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; + +use dpp::bls; +use dpp::dashcore::secp256k1::rand::SeedableRng; +use dpp::dashcore::secp256k1::{PublicKey, SecretKey}; use dpp::dashcore::signer; use dpp::ed25519_dalek::Signer as BlsSigner; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; @@ -14,7 +15,8 @@ use dpp::identity::{IdentityPublicKey, KeyType}; use dpp::platform_value::BinaryData; use dpp::state_transition::errors::InvalidIdentityPublicKeyTypeError; use dpp::util::hash::ripemd160_sha256; -use dpp::{bls_signatures, dashcore, ed25519_dalek, ProtocolError}; +use dpp::{dashcore, ed25519_dalek, ProtocolError}; +use rand::RngCore; use std::collections::BTreeMap; use std::fmt::{Debug, Formatter}; @@ -95,14 +97,14 @@ impl SimpleSigner { /// /// This is only for tests. pub fn add_random_address_key(&mut self, rng: &mut R) -> PlatformAddress { - let secp = Secp256k1::new(); - // Generate a valid secp256k1 secret key from random bytes - let mut ecdsa_rng = dashcore::secp256k1::rand::rngs::StdRng::from_rng(rng).unwrap(); + let mut seed = [0u8; 32]; + rng.fill_bytes(&mut seed); + let mut ecdsa_rng = dashcore::secp256k1::rand::rngs::StdRng::from_seed(seed); let secret_key = SecretKey::new(&mut ecdsa_rng); // Derive compressed public key - let public_key = PublicKey::from_secret_key(&secp, &secret_key); + let public_key = PublicKey::from_secret_key(&secret_key); let pubkey_ser = public_key.serialize(); // 33-byte compressed let address_hash = ripemd160_sha256(&pubkey_ser); @@ -110,7 +112,7 @@ impl SimpleSigner { // Store private key so this signer can later sign for this address // (use *_in_creation to mirror your identity key behavior) self.address_private_keys_in_creation - .insert(address_hash, secret_key.secret_bytes()); + .insert(address_hash, secret_key.to_secret_bytes()); PlatformAddress::P2pkh(address_hash) } @@ -137,15 +139,13 @@ impl Signer for SimpleSigner { Ok(signature.to_vec().into()) } KeyType::BLS12_381 => { - let pk = bls_signatures::SecretKey::::from_be_bytes(private_key) - .into_option() - .ok_or(ProtocolError::Generic( - "bls private key from bytes isn't correct".to_string(), - ))?; + let pk = bls::SecretKey::from_be_bytes(private_key).ok_or( + ProtocolError::Generic("bls private key from bytes isn't correct".to_string()), + )?; let signature = pk - .sign(SignatureSchemes::Basic, data) + .sign(data) .map_err(|e| ProtocolError::Generic(format!("BLS signing failed {}", e)))?; - Ok(signature.as_raw_value().to_compressed().to_vec().into()) + Ok(signature.to_bytes().to_vec().into()) } KeyType::EDDSA_25519_HASH160 => { #[allow(clippy::unnecessary_fallible_conversions)] diff --git a/packages/simple-signer/src/single_key_signer.rs b/packages/simple-signer/src/single_key_signer.rs index ad76b20af51..30cd749ed28 100644 --- a/packages/simple-signer/src/single_key_signer.rs +++ b/packages/simple-signer/src/single_key_signer.rs @@ -114,7 +114,7 @@ impl Signer for SingleKeySigner { KeyType::ECDSA_SECP256K1 | KeyType::ECDSA_HASH160 => { // Do not log private key material. Log data fingerprint only. debug!(data_hex = %hex::encode(data), "SingleKeySigner: signing data"); - let secret_bytes = Zeroizing::new(self.private_key.inner.secret_bytes()); + let secret_bytes = Zeroizing::new(self.private_key.inner.to_secret_bytes()); let signature = signer::sign(data, &secret_bytes[..])?; Ok(signature.to_vec().into()) } @@ -156,9 +156,8 @@ impl Signer for SingleKeySigner { match identity_public_key.key_type() { KeyType::ECDSA_SECP256K1 => { // Compare full public key - let secp = dashcore::secp256k1::Secp256k1::new(); let public_key = - dashcore::secp256k1::PublicKey::from_secret_key(&secp, &self.private_key.inner); + dashcore::secp256k1::PublicKey::from_secret_key(&self.private_key.inner); let public_key_bytes = public_key.serialize(); identity_public_key.data().as_slice() == public_key_bytes @@ -167,9 +166,8 @@ impl Signer for SingleKeySigner { // Compare hash160 of public key use dpp::dashcore::hashes::{hash160, Hash}; - let secp = dashcore::secp256k1::Secp256k1::new(); let public_key = - dashcore::secp256k1::PublicKey::from_secret_key(&secp, &self.private_key.inner); + dashcore::secp256k1::PublicKey::from_secret_key(&self.private_key.inner); let public_key_bytes = public_key.serialize(); let public_key_hash160 = hash160::Hash::hash(&public_key_bytes) .to_byte_array() @@ -222,7 +220,7 @@ mod tests { let hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; let signer = SingleKeySigner::from_hex(hex, Network::Testnet) .map_err(|e| format!("signer init failed: {}", e))?; - assert_eq!(signer.private_key().inner.secret_bytes().len(), 32); + assert_eq!(signer.private_key().inner.to_secret_bytes().len(), 32); Ok(()) } @@ -250,10 +248,8 @@ mod tests { use dpp::dashcore::hashes::{hash160, Hash}; let signer = SingleKeySigner::new_from_slice(&[0x03; 32], Network::Testnet)?; - let secp = dashcore::secp256k1::Secp256k1::new(); let public_key = - dashcore::secp256k1::PublicKey::from_secret_key(&secp, &signer.private_key.inner) - .serialize(); + dashcore::secp256k1::PublicKey::from_secret_key(&signer.private_key.inner).serialize(); let full_key = identity_key(KeyType::ECDSA_SECP256K1, public_key.to_vec()); assert!(signer.can_sign_with(&full_key)); @@ -275,7 +271,7 @@ mod tests { let hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; let signer = SingleKeySigner::from_string(hex, Network::Testnet) .map_err(|e| format!("signer init failed: {}", e))?; - assert_eq!(signer.private_key().inner.secret_bytes().len(), 32); + assert_eq!(signer.private_key().inner.to_secret_bytes().len(), 32); // Test WIF detection let private_key = PrivateKey::from_byte_array(&[0x02; 32], Network::Testnet) diff --git a/packages/strategy-tests/src/lib.rs b/packages/strategy-tests/src/lib.rs index f471c417d5c..9a8ad37941f 100644 --- a/packages/strategy-tests/src/lib.rs +++ b/packages/strategy-tests/src/lib.rs @@ -761,7 +761,7 @@ impl Strategy { let funding_transition = AddressFundingFromAssetLockTransitionV0::try_from_asset_lock_with_signer_and_private_key( asset_lock_proof, - private_key.inner.secret_bytes().as_slice(), + private_key.inner.to_secret_bytes().as_slice(), BTreeMap::new(), // no additional inputs outputs, vec![AddressFundsFeeStrategyStep::ReduceOutput(0)], @@ -2206,7 +2206,7 @@ impl Strategy { let funding_transition = AddressFundingFromAssetLockTransitionV0::try_from_asset_lock_with_signer_and_private_key( asset_lock_proof, - private_key.inner.secret_bytes().as_slice(), + private_key.inner.to_secret_bytes().as_slice(), BTreeMap::new(), // no additional inputs outputs, vec![AddressFundsFeeStrategyStep::ReduceOutput(0)], diff --git a/packages/strategy-tests/src/transitions.rs b/packages/strategy-tests/src/transitions.rs index d954e502009..bf678e6b412 100644 --- a/packages/strategy-tests/src/transitions.rs +++ b/packages/strategy-tests/src/transitions.rs @@ -32,7 +32,6 @@ //! each action. use dpp::address_funds::PlatformAddress; -use dpp::dashcore::secp256k1::Secp256k1; use dpp::dashcore::secp256k1::SecretKey; use dpp::dashcore::{ bls_sig_utils::BLSSignature, hash_types::CycleHash, InstantLock, OutPoint, ScriptBuf, @@ -173,14 +172,12 @@ pub fn instant_asset_lock_proof_fixture_with_dynamic_range( pub fn instant_asset_lock_proof_transaction_fixture( one_time_private_key: PrivateKey, ) -> Transaction { - let secp = Secp256k1::new(); - let private_key_hex = "cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY"; let private_key = PrivateKey::from_str(private_key_hex).unwrap(); - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); let public_key_hash = public_key.pubkey_hash(); //let from_address = Address::p2pkh(&public_key, Network::Testnet); - let one_time_public_key = one_time_private_key.public_key(&secp); + let one_time_public_key = one_time_private_key.public_key(); // We are going to fund 1 Dash and // assume that input has 100005000 @@ -251,14 +248,12 @@ pub fn instant_asset_lock_proof_transaction_fixture_with_dynamic_amount( amount_range: &AmountRange, rng: &mut StdRng, ) -> Transaction { - let secp = Secp256k1::new(); - let private_key_hex = "cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY"; let private_key = PrivateKey::from_str(private_key_hex).unwrap(); - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); let public_key_hash = public_key.pubkey_hash(); //let from_address = Address::p2pkh(&public_key, Network::Testnet); - let one_time_public_key = one_time_private_key.public_key(&secp); + let one_time_public_key = one_time_private_key.public_key(); // We are going to fund 1 Dash and // assume that input has 100005000 diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift index 53e279799ac..c989871e7a6 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerSPV.swift @@ -230,7 +230,8 @@ extension PlatformWalletManager { } } - /// Whether the SPV client is currently running. + /// Whether SPV sync is running or starting. `false` once background + /// sync has failed, until SPV is stopped and started again. public func isSpvRunning() throws -> Bool { try ensureConfigured() return try Self.readIsSpvRunning(handle) @@ -315,6 +316,9 @@ extension PlatformWalletManager { /// Throws `walletOperation` while an async [`stopSpv()`] is in flight: /// that stop is already tearing the client down, and this blocking one /// would wait on the same teardown on the calling thread. + /// + /// Throws `shutdownIncomplete` when teardown is still running (stop + /// again) and `spvProcessRestartRequired` when the app must restart. public func stopSpv() throws { try ensureNoSpvStopInFlight(before: "a blocking stopSpv()") try platform_wallet_manager_spv_stop(handle).check() diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift index 7fcfa941f44..b947a83272b 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift @@ -88,6 +88,8 @@ public enum PlatformWalletResultCode: Int32, Sendable { /// no state was wiped — and the caller should retry once sync is idle. /// (Not returned by `destroy`: Rust owns the callback contexts, so a /// straggling worker is memory-safe and merely logged there.) + /// Also returned by SPV start, stop and storage clear while an SPV + /// teardown is still running: stop SPV again. case errorShutdownIncomplete = 27 /// Asset-lock coin selection came up short over the *permitted* funding /// set (dashpay/platform#4073). Nothing was built or broadcast and no @@ -258,6 +260,8 @@ public enum PlatformWalletResultCode: Int32, Sendable { case errorShieldedRecoveryKeysRequired = 57 /// Platform returned no balance. Retrying the read is safe; ownership is unchanged. case errorIdentityBalanceUnavailable = 58 + /// SPV startup or teardown panicked. Not retryable: restart the app. + case errorSpvProcessRestartRequired = 59 /// The named thing does not exist. Besides the handle/lookup failures this /// has always covered, BOTH deferred-send paths report the /// wallet-was-REMOVED case here. @@ -385,6 +389,8 @@ public enum PlatformWalletResultCode: Int32, Sendable { self = .errorShieldedRecoveryKeysRequired case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_IDENTITY_BALANCE_UNAVAILABLE: self = .errorIdentityBalanceUnavailable + case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_SPV_PROCESS_RESTART_REQUIRED: + self = .errorSpvProcessRestartRequired case PLATFORM_WALLET_FFI_RESULT_CODE_NOT_FOUND: self = .notFound case PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_UNKNOWN: @@ -626,8 +632,13 @@ public enum PlatformWalletError: LocalizedError { case addressNonceMismatch(String) /// A quiesce/drain barrier (Clear / reset / sync-stop) timed out with a /// sync pass still in flight. The operation failed closed — retry once - /// sync is idle. + /// sync is idle. SPV start, stop and storage clear also throw it while an + /// SPV teardown is still running: call `stopSpv()` again. case shutdownIncomplete(String) + /// SPV startup or teardown panicked. Not retryable: restart the app. + /// `errorDescription` is fixed user text; the panic detail is on + /// `failureReason`. + case spvProcessRestartRequired(String) /// The signer has no usable private key for the requested public key /// (missing / stranded scalar) — the operation itself did not fail. /// Restored from the structured signer completion code @@ -822,6 +833,8 @@ public enum PlatformWalletError: LocalizedError { return "The wallet data could not be read and may need to be restored." case .persisterStoreFatal, .persisterStoreConstraint: return "The wallet data could not be saved and may need to be restored." + case .spvProcessRestartRequired: + return "Sync could not be stopped cleanly. Restart the app to use it again." // The three value-carrying marketplace rejections compose their // description from the typed values, because their FFI message is // the machine-readable JSON detail — showing that raw would be @@ -848,7 +861,8 @@ public enum PlatformWalletError: LocalizedError { switch self { case .persisterLoadTransient(let m), .persisterLoadFatal(let m), .persisterStoreTransient(let m), .persisterStoreFatal(let m), - .persisterStoreConstraint(let m), .persisterRestore(let m): + .persisterStoreConstraint(let m), .persisterRestore(let m), + .spvProcessRestartRequired(let m): return m default: return nil @@ -920,6 +934,8 @@ public enum PlatformWalletError: LocalizedError { self = .addressNonceMismatch(detail) case .errorShutdownIncomplete: self = .shutdownIncomplete(detail) + case .errorSpvProcessRestartRequired: + self = .spvProcessRestartRequired(detail) case .errorSigningKeyUnavailable: self = .signingKeyUnavailable(detail) case .errorStaleReservationToken: diff --git a/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/ErrorHandlingTests.swift b/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/ErrorHandlingTests.swift index 4f0290f3ede..f18b44a9fce 100644 --- a/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/ErrorHandlingTests.swift +++ b/packages/swift-sdk/SwiftTests/SwiftDashSDKTests/ErrorHandlingTests.swift @@ -439,6 +439,26 @@ final class ErrorHandlingTests: XCTestCase { XCTAssertEqual(keyMessage, detail) } + func testShouldPreserveSpvProcessRestartRequiredFromFFI() { + let code = PlatformWalletResultCode( + ffi: PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_SPV_PROCESS_RESTART_REQUIRED + ) + XCTAssertEqual(code, .errorSpvProcessRestartRequired) + XCTAssertEqual(code.rawValue, 59) + let detail = "teardown task 12 panicked with message \"boom\"" + let error = PlatformWalletError(code: code, message: detail) + guard case .spvProcessRestartRequired(let message) = error else { + return XCTFail("lost typed restart-required error") + } + XCTAssertEqual(message, detail) + // The panic detail is log material; the alert text is fixed. + XCTAssertEqual(error.failureReason, detail) + XCTAssertEqual( + error.errorDescription, + "Sync could not be stopped cleanly. Restart the app to use it again." + ) + } + func testShouldPreserveShieldedIdentityDebitPendingFFIResult() { let code = PlatformWalletResultCode( ffi: PLATFORM_WALLET_FFI_RESULT_CODE_ERROR_SHIELDED_IDENTITY_DEBIT_PENDING diff --git a/packages/swift-sdk/run_integration_tests.sh b/packages/swift-sdk/run_integration_tests.sh index 1177a642dd3..938858d275d 100755 --- a/packages/swift-sdk/run_integration_tests.sh +++ b/packages/swift-sdk/run_integration_tests.sh @@ -13,12 +13,12 @@ REPO_ROOT="$( cd "$SCRIPT_DIR/../.." && pwd )" cd "$SCRIPT_DIR" -# dashmate pins Node 20–22; bail early instead of failing several +# dashmate requires Node 24+; bail early instead of failing several # minutes into the dashmate startup with a cryptic error. node_major=$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0) -if [ "$node_major" -gt 22 ] || [ "$node_major" -lt 20 ]; then +if [ "$node_major" -lt 24 ]; then echo "Node $(node --version 2>/dev/null || echo '') is not supported by dashmate." - echo "Use Node 20–22 (e.g. \`nvm use 22\`) and re-run." + echo "Use Node 24 LTS (e.g. \`nvm use 24\`) and re-run." exit 1 fi diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index c8ee66ae754..d1a3501cf54 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -5,6 +5,9 @@ "main": "src/index.js", "unpkg": "dist/wallet-lib.min.js", "types": "src/index.d.ts", + "engines": { + "node": ">=22" + }, "scripts": { "build:web": "webpack --stats-error-details", "lint": "eslint .", diff --git a/packages/wasm-dpp/README.md b/packages/wasm-dpp/README.md index c1da995eba0..774f56513cb 100644 --- a/packages/wasm-dpp/README.md +++ b/packages/wasm-dpp/README.md @@ -7,6 +7,8 @@ The WASM JavaScript binding of the Rust implementation of the [Dash Platform Protocol](https://dashplatform.readme.io/docs/explanation-platform-protocol) +Requires Node.js >= 22 when used in Node.js. + ### THIS IS A DEV VERSION, NOT INTENDED FOR A PRODUCTION USAGE JUST YET ## Dev environment diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index afad8405fa9..b989ba92b41 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -4,6 +4,9 @@ "description": "The JavaScript implementation of the Dash Platform Protocol", "main": "dist/index.js", "types": "dist/index.d.ts", + "engines": { + "node": ">=22" + }, "scripts": { "build": "yarn run build:wasm && yarn run build:js", "build:wasm": "yarn exec scripts/build-wasm.sh", @@ -46,7 +49,7 @@ "@dashevo/dashcore-lib": "~0.22.0", "@dashevo/dpns-contract": "workspace:*", "@types/bs58": "^4.0.1", - "@types/node": "^20.10.0", + "@types/node": "^22.0.0", "@yarnpkg/pnpify": "^4.0.0-rc.42", "ajv": "^8.18.0", "assert": "^2.0.0", diff --git a/packages/wasm-dpp2/package.json b/packages/wasm-dpp2/package.json index 643ede05350..b4a63215ac7 100644 --- a/packages/wasm-dpp2/package.json +++ b/packages/wasm-dpp2/package.json @@ -25,7 +25,7 @@ ], "sideEffects": false, "engines": { - "node": ">=18.18" + "node": ">=22" }, "scripts": { "build": "./scripts/build.sh && node ./scripts/bundle.cjs", @@ -42,7 +42,7 @@ "devDependencies": { "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", - "@types/node": "^20.10.0", + "@types/node": "^22.0.0", "assert": "^2.0.0", "bs58": "^4.0.1", "buffer": "^6.0.3", diff --git a/packages/wasm-dpp2/src/core/private_key.rs b/packages/wasm-dpp2/src/core/private_key.rs index 17a41c969e8..fc411d06d10 100644 --- a/packages/wasm-dpp2/src/core/private_key.rs +++ b/packages/wasm-dpp2/src/core/private_key.rs @@ -8,9 +8,6 @@ use crate::impl_wasm_type_info; use crate::public_key::PublicKeyWasm; use crate::utils::try_vec_to_fixed_bytes; use dpp::dashcore::PrivateKey; -use dpp::dashcore::hashes::hex::FromHex; -use dpp::dashcore::key::Secp256k1; -use dpp::dashcore::secp256k1::hashes::hex::{Case, DisplayHex}; use wasm_bindgen::prelude::wasm_bindgen; #[wasm_bindgen(js_name = "PrivateKey")] @@ -65,8 +62,8 @@ impl PrivateKeyWasm { ) -> WasmDppResult { let network_wasm: NetworkWasm = network.try_into()?; - let bytes = Vec::from_hex(hex_key) - .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; + let bytes = + hex::decode(hex_key).map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; let key_bytes: [u8; 32] = try_vec_to_fixed_bytes(bytes, "privateKey")?; @@ -78,9 +75,7 @@ impl PrivateKeyWasm { #[wasm_bindgen(js_name = "getPublicKey")] pub fn get_public_key(&self) -> PublicKeyWasm { - let secp = Secp256k1::new(); - - let public_key = self.0.public_key(&secp); + let public_key = self.0.public_key(); public_key.into() } @@ -100,14 +95,12 @@ impl PrivateKeyWasm { #[wasm_bindgen(js_name = "toHex")] pub fn to_hex(&self) -> String { - self.0.to_bytes().to_hex_string(Case::Upper) + hex::encode_upper(self.0.to_bytes()) } #[wasm_bindgen(js_name = "getPublicKeyHash")] pub fn get_public_key_hash(&self) -> String { - let secp = Secp256k1::new(); - - self.0.public_key(&secp).pubkey_hash().to_hex() + self.0.public_key().pubkey_hash().to_string() } } diff --git a/packages/wasm-dpp2/src/identity/public_key.rs b/packages/wasm-dpp2/src/identity/public_key.rs index 8e2c1b42fe8..578dba11293 100644 --- a/packages/wasm-dpp2/src/identity/public_key.rs +++ b/packages/wasm-dpp2/src/identity/public_key.rs @@ -14,7 +14,6 @@ use crate::utils::{ }; use crate::version::PlatformVersionLikeJs; use dpp::dashcore::Network; -use dpp::dashcore::secp256k1::hashes::hex::{Case, DisplayHex}; use dpp::fee::Credits; use dpp::identity::contract_bounds::ContractBounds; use dpp::identity::hash::IdentityPublicKeyHashMethodsV0; @@ -397,11 +396,7 @@ impl IdentityPublicKeyWasm { #[wasm_bindgen(js_name = "getPublicKeyHash")] pub fn public_key_hash(&self) -> WasmDppResult { - let hash = self - .0 - .public_key_hash()? - .to_vec() - .to_hex_string(Case::Lower); + let hash = hex::encode(self.0.public_key_hash()?); Ok(hash) } diff --git a/packages/wasm-dpp2/src/public_key.rs b/packages/wasm-dpp2/src/public_key.rs index abd1dfc8d01..49faa6feef7 100644 --- a/packages/wasm-dpp2/src/public_key.rs +++ b/packages/wasm-dpp2/src/public_key.rs @@ -39,7 +39,7 @@ impl PublicKeyWasm { public_key_bytes.try_into().map_err(|_| { WasmDppError::invalid_argument("compressed public key must contain 33 bytes") })?; - secp256k1::PublicKey::from_byte_array_compressed(&bytes) + secp256k1::PublicKey::from_byte_array_compressed(bytes) } else { if public_key_bytes.len() != constants::UNCOMPRESSED_PUBLIC_KEY_SIZE { return Err(WasmDppError::invalid_argument(format!( @@ -53,7 +53,7 @@ impl PublicKeyWasm { WasmDppError::invalid_argument("uncompressed public key must contain 65 bytes") })?; - secp256k1::PublicKey::from_byte_array_uncompressed(&bytes) + secp256k1::PublicKey::from_byte_array_uncompressed(bytes) } .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; @@ -87,7 +87,7 @@ impl PublicKeyWasm { })?; self.0.compressed = true; - self.0.inner = secp256k1::PublicKey::from_byte_array_compressed(&bytes) + self.0.inner = secp256k1::PublicKey::from_byte_array_compressed(bytes) .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; } else { if inner.len() != constants::UNCOMPRESSED_PUBLIC_KEY_SIZE { @@ -103,7 +103,7 @@ impl PublicKeyWasm { })?; self.0.compressed = false; - self.0.inner = secp256k1::PublicKey::from_byte_array_uncompressed(&bytes) + self.0.inner = secp256k1::PublicKey::from_byte_array_uncompressed(bytes) .map_err(|err| WasmDppError::invalid_argument(err.to_string()))?; } @@ -112,7 +112,7 @@ impl PublicKeyWasm { #[wasm_bindgen(js_name = "getPublicKeyHash")] pub fn get_public_key_hash(&self) -> String { - self.0.pubkey_hash().to_hex() + self.0.pubkey_hash().to_string() } #[wasm_bindgen(js_name = "toBytes")] diff --git a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs index b859cb1336a..1f7505bd0ad 100644 --- a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs +++ b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs @@ -7,8 +7,6 @@ use crate::identifier::{IdentifierLikeJs, IdentifierWasm}; use crate::identity::public_key::IdentityPublicKeyWasm; use crate::impl_wasm_type_info; use crate::mock_bls::MockBLS; -use dpp::dashcore::secp256k1::hashes::hex::Case::Lower; -use dpp::dashcore::secp256k1::hashes::hex::DisplayHex; use dpp::data_contract::serialized_version::DataContractInSerializationFormat; use dpp::identity::{KeyID, KeyType}; use dpp::platform_value::BinaryData; @@ -330,7 +328,7 @@ impl StateTransitionWasm { dpp::serialization::PlatformSerializable::serialize_to_bytes(&self.0)? }; - Ok(Sha256::digest(payload).to_hex_string(Lower)) + Ok(hex::encode(Sha256::digest(payload))) } #[wasm_bindgen(getter = "actionType")] diff --git a/packages/wasm-drive-verify/package.json b/packages/wasm-drive-verify/package.json index f09a090c5ca..6239309a755 100644 --- a/packages/wasm-drive-verify/package.json +++ b/packages/wasm-drive-verify/package.json @@ -52,6 +52,9 @@ "dist", "pkg" ], + "engines": { + "node": ">=22" + }, "scripts": { "build": "./build.sh", "build:modules": "./scripts/build-modules.sh" diff --git a/packages/wasm-sdk/README.md b/packages/wasm-sdk/README.md index 64bf9198866..47a70756c34 100644 --- a/packages/wasm-sdk/README.md +++ b/packages/wasm-sdk/README.md @@ -209,7 +209,7 @@ Accepted values are simple levels ('off'|'error'|'warn'|'info'|'debug'|'trace') ## Environment & compatibility - ESM-only package ("type": "module"). Use dynamic import in CJS. -- Node.js: 16+ recommended (18+ preferred). +- Node.js: >= 22. - Browsers: modern engines with WebAssembly + Web Workers. --- diff --git a/packages/wasm-sdk/package.json b/packages/wasm-sdk/package.json index c76b5decfbb..6541b388d97 100644 --- a/packages/wasm-sdk/package.json +++ b/packages/wasm-sdk/package.json @@ -25,7 +25,7 @@ ], "sideEffects": false, "engines": { - "node": ">=18.18" + "node": ">=22" }, "scripts": { "build": "./scripts/build.sh && node ./scripts/bundle.cjs", @@ -43,7 +43,7 @@ "devDependencies": { "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", - "@types/node": "^20.10.0", + "@types/node": "^22.0.0", "assert": "^2.0.0", "buffer": "^6.0.3", "chai": "^4.3.10", diff --git a/packages/wasm-sdk/src/encrypted_for.rs b/packages/wasm-sdk/src/encrypted_for.rs index 45e6f3575cc..5e86489b3c7 100644 --- a/packages/wasm-sdk/src/encrypted_for.rs +++ b/packages/wasm-sdk/src/encrypted_for.rs @@ -5,7 +5,7 @@ use crate::error::WasmSdkError; use crate::sdk::WasmSdk; -use dash_sdk::dpp::dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use dash_sdk::dpp::dashcore::secp256k1::{PublicKey, SecretKey}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; use dash_sdk::dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dash_sdk::dpp::data_contract::document_type::DocumentTypeRef; @@ -215,9 +215,7 @@ impl WasmSdk { // A private key that is not the sender key's would write a message nobody can read let sender_public_key = secp256k1_public_key(&sender_key, "senderKey")?; - if PublicKey::from_secret_key(&Secp256k1::signing_only(), &sender_private_key) - != sender_public_key - { + if PublicKey::from_secret_key(&sender_private_key) != sender_public_key { return Err(WasmSdkError::invalid_argument( "senderPrivateKey is not the private half of senderKey", )); diff --git a/packages/wasm-sdk/src/wallet/dip14.rs b/packages/wasm-sdk/src/wallet/dip14.rs index a1a5c3a3b56..1deb3fb80e1 100644 --- a/packages/wasm-sdk/src/wallet/dip14.rs +++ b/packages/wasm-sdk/src/wallet/dip14.rs @@ -4,7 +4,7 @@ //! instead of the standard 31-bit limitation. use dash_sdk::dpp::dashcore::hashes::{sha256, Hash}; -use dash_sdk::dpp::dashcore::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; +use dash_sdk::dpp::dashcore::secp256k1::{self, PublicKey, Scalar, SecretKey}; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::key_wallet; use dash_sdk::dpp::key_wallet::bip32::{ExtendedPrivKey, ExtendedPubKey}; @@ -97,8 +97,6 @@ impl Dip14ExtendedPrivKey { /// Derive a child key using DIP14 extended derivation pub fn derive_child(&self, index: &[u8; 32], hardened: bool) -> Result { - let secp = Secp256k1::new(); - // Prepare HMAC input based on hardened flag let mut hmac = HmacSha512::new_from_slice(&self.chain_code) .map_err(|_| Dip14Error::DerivationFailed("Invalid chain code".to_string()))?; @@ -106,11 +104,11 @@ impl Dip14ExtendedPrivKey { if hardened { // Hardened: 0x00 || ser256(k_parent) || ser256(i) hmac.update(&[0x00]); - hmac.update(&self.private_key.secret_bytes()); + hmac.update(&self.private_key.to_secret_bytes()); hmac.update(&ser256(index)); } else { // Non-hardened: ser_P(point(k_parent)) || ser256(i) - let public_key = PublicKey::from_secret_key(&secp, &self.private_key); + let public_key = PublicKey::from_secret_key(&self.private_key); hmac.update(&public_key.serialize()); hmac.update(&ser256(index)); } @@ -122,7 +120,10 @@ impl Dip14ExtendedPrivKey { // This is the core of BIP32/DIP14 child key derivation // First, try to create a secret key from IL - let il_scalar = match SecretKey::from_slice(il_bytes) { + let il_scalar = match <[u8; 32]>::try_from(il_bytes) + .map_err(|_| secp256k1::Error::InvalidSecretKey) + .and_then(SecretKey::from_secret_bytes) + { Ok(key) => key, Err(_) => { return Err(Dip14Error::DerivationFailed( @@ -134,7 +135,7 @@ impl Dip14ExtendedPrivKey { // Add parent key to IL // In secp256k1, we perform scalar addition: child_key = parent_key + IL (mod n) // Convert IL to a Scalar for the tweak operation - let il_scalar_bytes = il_scalar.secret_bytes(); + let il_scalar_bytes = il_scalar.to_secret_bytes(); let tweak = Scalar::from_be_bytes(il_scalar_bytes).map_err(|_| { Dip14Error::DerivationFailed("Failed to convert IL to scalar".to_string()) })?; @@ -146,7 +147,7 @@ impl Dip14ExtendedPrivKey { .map_err(|e| Dip14Error::DerivationFailed(format!("Failed to add tweak: {}", e)))?; // Calculate parent fingerprint (first 4 bytes of parent pubkey hash160) - let parent_pubkey = PublicKey::from_secret_key(&secp, &self.private_key); + let parent_pubkey = PublicKey::from_secret_key(&self.private_key); // Use sha256 then ripemd160 to create hash160 let sha256_hash = sha256::Hash::hash(&parent_pubkey.serialize()); let parent_pubkey_hash = @@ -167,8 +168,8 @@ impl Dip14ExtendedPrivKey { } /// Get the extended public key - pub fn to_extended_pub_key(&self, secp: &Secp256k1) -> Dip14ExtendedPubKey { - let public_key = PublicKey::from_secret_key(secp, &self.private_key); + pub fn to_extended_pub_key(&self) -> Dip14ExtendedPubKey { + let public_key = PublicKey::from_secret_key(&self.private_key); Dip14ExtendedPubKey { network: self.network, diff --git a/packages/wasm-sdk/src/wallet/extended_derivation.rs b/packages/wasm-sdk/src/wallet/extended_derivation.rs index b8a929605df..8a1de840261 100644 --- a/packages/wasm-sdk/src/wallet/extended_derivation.rs +++ b/packages/wasm-sdk/src/wallet/extended_derivation.rs @@ -7,7 +7,6 @@ use crate::impl_wasm_serde_conversions; use crate::queries::utils::deserialize_required_query; use crate::sdk::WasmSdk; use dash_sdk::dpp::dashcore; -use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; use dash_sdk::dpp::key_wallet::{bip32, DerivationPath, ExtendedPrivKey}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -109,20 +108,19 @@ fn derive_common_from_mnemonic( let derivation_path = DerivationPath::from_str(path) .map_err(|e| WasmSdkError::invalid_argument(format!("Invalid derivation path: {}", e)))?; - let secp = Secp256k1::new(); let derived_key = master_key - .derive_priv(&secp, &derivation_path) + .derive_priv(&derivation_path) .map_err(|e| WasmSdkError::generic(format!("Failed to derive key: {}", e)))?; - let xpub = bip32::ExtendedPubKey::from_priv(&secp, &derived_key); + let xpub = bip32::ExtendedPubKey::from_priv(&derived_key); let private_key = dashcore::PrivateKey::new(derived_key.private_key, net); - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); let address = dashcore::Address::p2pkh(&public_key, net); Ok(CommonDerivation { path: path.to_string(), private_key_wif: private_key.to_wif(), - private_key_hex: hex::encode(private_key.inner.secret_bytes()), + private_key_hex: hex::encode(private_key.inner.to_secret_bytes()), public_key_hex: hex::encode(public_key.to_bytes()), address: address.to_string(), network: network.to_string(), diff --git a/packages/wasm-sdk/src/wallet/key_derivation.rs b/packages/wasm-sdk/src/wallet/key_derivation.rs index ad5db95a1b5..9423e329d74 100644 --- a/packages/wasm-sdk/src/wallet/key_derivation.rs +++ b/packages/wasm-sdk/src/wallet/key_derivation.rs @@ -8,7 +8,6 @@ use crate::queries::utils::{deserialize_query_with_default, deserialize_required use crate::sdk::WasmSdk; use bip39::{Language, Mnemonic}; use dash_sdk::dpp::dashcore; -use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; use dash_sdk::dpp::key_wallet::bip32::{ ChildNumber, DerivationPath as BIP32DerivationPath, ExtendedPrivKey as BIP32ExtendedPrivKey, ExtendedPubKey as BIP32ExtendedPubKey, @@ -430,10 +429,7 @@ impl WasmSdk { .map_err(|e| WasmSdkError::generic(format!("Failed to create private key: {}", e)))?; // Get public key - use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; - let secp = Secp256k1::new(); - - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); let public_key_bytes = public_key.inner.serialize(); // Get address let address = dashcore::Address::p2pkh(&public_key, net); @@ -483,7 +479,7 @@ impl WasmSdk { // Derive the key at the specified path let derived_key = master_key - .derive_priv(&dashcore::secp256k1::Secp256k1::new(), &derivation_path) + .derive_priv(&derivation_path) .map_err(|e| WasmSdkError::generic(format!("Failed to derive key: {}", e)))?; // In v0.40-dev, ExtendedPrivKey might have a different structure @@ -491,8 +487,7 @@ impl WasmSdk { let private_key = dashcore::PrivateKey::new(derived_key.private_key, net); // Get public key - let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); // Get address let address = dashcore::Address::p2pkh(&public_key, net); @@ -500,7 +495,7 @@ impl WasmSdk { Ok(PathDerivedKeyInfoWasm { path, private_key_wif: private_key.to_wif(), - private_key_hex: hex::encode(private_key.inner.secret_bytes()), + private_key_hex: hex::encode(private_key.inner.to_secret_bytes()), public_key: hex::encode(public_key.to_bytes()), address: address.to_string(), network, @@ -613,9 +608,8 @@ impl WasmSdk { // Build a one-step derivation path and derive let child_number: ChildNumber = ChildNumber::from(index); let path = BIP32DerivationPath::from(vec![child_number]); - let secp = Secp256k1::new(); let child_xpub = parent_xpub - .derive_pub(&secp, &path) + .derive_pub(&path) .map_err(|e| WasmSdkError::generic(format!("Failed to derive child key: {}", e)))?; Ok(child_xpub.to_string()) @@ -628,8 +622,7 @@ impl WasmSdk { let ext_prv = BIP32ExtendedPrivKey::from_str(xprv).map_err(|e| { WasmSdkError::invalid_argument(format!("Invalid extended private key: {}", e)) })?; - let secp = Secp256k1::new(); - let ext_pub = BIP32ExtendedPubKey::from_priv(&secp, &ext_prv); + let ext_pub = BIP32ExtendedPubKey::from_priv(&ext_prv); Ok(ext_pub.to_string()) } } diff --git a/packages/wasm-sdk/src/wallet/key_generation.rs b/packages/wasm-sdk/src/wallet/key_generation.rs index e1678d374fa..8830b5b13e3 100644 --- a/packages/wasm-sdk/src/wallet/key_generation.rs +++ b/packages/wasm-sdk/src/wallet/key_generation.rs @@ -6,7 +6,7 @@ use crate::error::WasmSdkError; use crate::impl_wasm_serde_conversions; use crate::sdk::WasmSdk; use dash_sdk::dpp::dashcore::hashes::{sha256, Hash}; -use dash_sdk::dpp::dashcore::secp256k1::{Secp256k1, SecretKey}; +use dash_sdk::dpp::dashcore::secp256k1::SecretKey; use dash_sdk::dpp::dashcore::{Address, Network, PrivateKey, PublicKey}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -67,14 +67,13 @@ impl WasmSdk { network: Network, network_label: &str, ) -> Result { - let secp = Secp256k1::new(); - let public_key = private_key.public_key(&secp); + let public_key = private_key.public_key(); let public_key_bytes = public_key.inner.serialize(); let address = Address::p2pkh(&public_key, network); Ok(KeyPair { private_key_wif: private_key.to_wif(), - private_key_hex: hex::encode(private_key.inner.secret_bytes()), + private_key_hex: hex::encode(private_key.inner.to_secret_bytes()), public_key: hex::encode(public_key_bytes), address: address.to_string(), network: network_label.to_string(), @@ -96,7 +95,7 @@ impl WasmSdk { .map_err(|e| WasmSdkError::generic(format!("Failed to create private key: {}", e)))?; // Ensure secret key is valid before building info - SecretKey::from_slice(&key_bytes) + SecretKey::from_secret_bytes(key_bytes) .map_err(|e| WasmSdkError::invalid_argument(format!("Invalid secret key: {}", e)))?; let key_pair = Self::build_key_pair(&private_key, net, network_wasm.as_str())?; @@ -221,13 +220,12 @@ impl WasmSdk { let hash = sha256::Hash::hash(message_bytes); // Sign the hash - let secp = Secp256k1::new(); - let secret_key = SecretKey::from_slice(&private_key.inner.secret_bytes()) + let secret_key = SecretKey::from_secret_bytes(private_key.inner.to_secret_bytes()) .map_err(|e| WasmSdkError::invalid_argument(format!("Invalid secret key: {}", e)))?; let message_hash = dash_sdk::dpp::dashcore::secp256k1::Message::from_digest(hash.to_byte_array()); - let signature = secp.sign_ecdsa(&message_hash, &secret_key); + let signature = secret_key.sign_ecdsa(message_hash); Ok(hex::encode(signature.serialize_compact())) } diff --git a/qa-contract/README.md b/qa-contract/README.md index a1b4bef2057..f172577e32f 100644 --- a/qa-contract/README.md +++ b/qa-contract/README.md @@ -199,7 +199,7 @@ Leave `testCase` as OwnerOnly so the canonical catalog stays curated. ## Install & run The scripts use the recommended **`@dashevo/evo-sdk`** (js-evo-sdk) in trusted -mode (required so state-transition responses are proof-verified). Node ≥ 18.18. +mode (required so state-transition responses are proof-verified). Node ≥ 22. **Option A — standalone (published SDK):** diff --git a/qa-contract/package.json b/qa-contract/package.json index 38836429ddf..2c8618176f3 100644 --- a/qa-contract/package.json +++ b/qa-contract/package.json @@ -5,7 +5,7 @@ "type": "module", "description": "On-chain QA framework storage layer (data contract + scripts) for Dash Platform testnet.", "engines": { - "node": ">=18.18" + "node": ">=22" }, "scripts": { "register": "node src/register.mjs", diff --git a/scripts/setup-ai-agent-environment.sh b/scripts/setup-ai-agent-environment.sh index 069cf901882..83543a865e5 100755 --- a/scripts/setup-ai-agent-environment.sh +++ b/scripts/setup-ai-agent-environment.sh @@ -77,16 +77,16 @@ run_apt install -y --no-install-recommends \ xz-utils \ zip -log "Ensuring Node.js 20 LTS via NodeSource" +log "Ensuring Node.js 24 LTS via NodeSource" NODE_MAJOR=0 if command -v node >/dev/null 2>&1; then NODE_MAJOR=$(node --version | sed 's/v\([0-9]*\).*/\1/') fi -if (( NODE_MAJOR < 20 )); then +if (( NODE_MAJOR < 24 )); then if [[ -n "${SUDO}" ]]; then - curl -fsSL https://deb.nodesource.com/setup_20.x | ${SUDO} -E bash - + curl -fsSL https://deb.nodesource.com/setup_24.x | ${SUDO} -E bash - else - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + curl -fsSL https://deb.nodesource.com/setup_24.x | bash - fi run_apt install -y --no-install-recommends nodejs else diff --git a/yarn.lock b/yarn.lock index 884fa0a45c6..591f83ff3dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1785,7 +1785,7 @@ __metadata: "@dashevo/wasm-sdk": "workspace:*" "@types/chai": "npm:^4.3.11" "@types/mocha": "npm:^10.0.6" - "@types/node": "npm:^20.10.0" + "@types/node": "npm:^22.0.0" "@types/sinon": "npm:^9.0.4" "@types/sinon-chai": "npm:^3.2.4" assert: "npm:^2.0.0" @@ -2087,7 +2087,7 @@ __metadata: dependencies: "@types/chai": "npm:^4.3.11" "@types/mocha": "npm:^10.0.6" - "@types/node": "npm:^20.10.0" + "@types/node": "npm:^22.0.0" assert: "npm:^2.0.0" bs58: "npm:^4.0.1" buffer: "npm:^6.0.3" @@ -2129,7 +2129,7 @@ __metadata: "@dashevo/dashcore-lib": "npm:~0.22.0" "@dashevo/dpns-contract": "workspace:*" "@types/bs58": "npm:^4.0.1" - "@types/node": "npm:^20.10.0" + "@types/node": "npm:^22.0.0" "@yarnpkg/pnpify": "npm:^4.0.0-rc.42" ajv: "npm:^8.18.0" assert: "npm:^2.0.0" @@ -2180,7 +2180,7 @@ __metadata: dependencies: "@types/chai": "npm:^4.3.11" "@types/mocha": "npm:^10.0.6" - "@types/node": "npm:^20.10.0" + "@types/node": "npm:^22.0.0" assert: "npm:^2.0.0" buffer: "npm:^6.0.3" chai: "npm:^4.3.10" @@ -3937,12 +3937,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.10.0": - version: 20.19.30 - resolution: "@types/node@npm:20.19.30" +"@types/node@npm:^22.0.0": + version: 22.20.5 + resolution: "@types/node@npm:22.20.5" dependencies: undici-types: "npm:~6.21.0" - checksum: 10/4a25e5cbcdfc61b9bf45ebbbd199a6ce26efed34bd67c45c3472654a1cf988f7b58ec7c1574ffb5b83c6a772e7eaf1ca9de94fdafc3a36ae6efb852c1f0b6fb0 + checksum: 10/a223a73e00e948b751b00144523674ecc73e7a3f14f48a0f3a6fca40745ff548a8b0ff5baca090d45ae8b61481cc56c1158db4df312970baeb6fc1b81fc511a1 languageName: node linkType: hard @@ -7282,7 +7282,7 @@ __metadata: "@types/chai": "npm:^4.3.11" "@types/dirty-chai": "npm:^2.0.2" "@types/mocha": "npm:^10.0.6" - "@types/node": "npm:^20.10.0" + "@types/node": "npm:^22.0.0" "@types/sinon": "npm:^9.0.4" "@types/sinon-chai": "npm:^3.2.4" "@yarnpkg/pnpify": "npm:^4.0.0-rc.42"