From 6a3439aaad20bbd481531a552cb84b678668b92e Mon Sep 17 00:00:00 2001 From: ktechmidas <9920871+ktechmidas@users.noreply.github.com> Date: Thu, 5 Feb 2026 08:32:09 +0300 Subject: [PATCH 1/8] fix(dashmate): letsencrypt renewal and dashmate doctor fixes (#3018) Co-authored-by: ktechmidas Co-authored-by: Ivan Shumkov Co-authored-by: Claude Opus 4.5 Co-authored-by: Ivan Shumkov --- .yarn/cache/fsevents-patch-19706e7e35-10.zip | Bin 0 -> 23750 bytes CHANGELOG.md | 14 ++++ Cargo.lock | 76 +++++++++--------- Cargo.toml | 2 +- package.json | 2 +- packages/bench-suite/package.json | 2 +- packages/dapi-grpc/package.json | 2 +- packages/dapi/package.json | 2 +- packages/dash-spv/package.json | 2 +- packages/dashmate/package.json | 2 +- .../doctor/analyse/analyseConfigFactory.js | 52 +++++++++--- .../tasks/doctor/collectSamplesTaskFactory.js | 24 ++++++ ...obtainLetsEncryptCertificateTaskFactory.js | 55 ++++++++++--- packages/dashpay-contract/package.json | 2 +- packages/dpns-contract/package.json | 2 +- packages/feature-flags-contract/package.json | 2 +- packages/js-dapi-client/package.json | 2 +- packages/js-dash-sdk/package.json | 2 +- packages/js-evo-sdk/package.json | 2 +- packages/js-grpc-common/package.json | 2 +- packages/keyword-search-contract/package.json | 2 +- .../package.json | 2 +- packages/platform-test-suite/package.json | 2 +- packages/token-history-contract/package.json | 2 +- packages/wallet-lib/package.json | 2 +- packages/wallet-utils-contract/package.json | 2 +- packages/wasm-dpp/package.json | 2 +- packages/wasm-dpp2/package.json | 2 +- packages/wasm-drive-verify/package.json | 2 +- packages/wasm-sdk/package.json | 2 +- packages/withdrawals-contract/package.json | 2 +- 31 files changed, 187 insertions(+), 84 deletions(-) create mode 100644 .yarn/cache/fsevents-patch-19706e7e35-10.zip diff --git a/.yarn/cache/fsevents-patch-19706e7e35-10.zip b/.yarn/cache/fsevents-patch-19706e7e35-10.zip new file mode 100644 index 0000000000000000000000000000000000000000..aff1ab12ce57f312cc3bc58c78597020f7980e62 GIT binary patch literal 23750 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 literal 0 HcmV?d00001 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8212fee199d..65cb35b0492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +### [3.0.1-hotfix.2](https://github.com/dashpay/platform/compare/v3.0.1-hotfix.1...v3.0.1-hotfix.2) (2026-02-02) + + +### Bug Fixes + +* **dashmate:** pass --profile shortlived on letsencrypt renewal + +### [3.0.1-hotfix.1](https://github.com/dashpay/platform/compare/v3.0.0...v3.0.1-hotfix.1) (2026-01-26) + + +### Bug Fixes + +* use single quotes and preserve ctx values in merge + ## [3.0.0-rc.3](///compare/v3.0.0-rc.2...v3.0.0-rc.3) (2026-01-20) diff --git a/Cargo.lock b/Cargo.lock index 285fef55afc..ea1f91e7515 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,7 +856,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "check-features" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "toml 0.8.23", ] @@ -1290,7 +1290,7 @@ dependencies = [ [[package]] name = "dapi-grpc" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "dash-platform-macros", "futures-core", @@ -1378,7 +1378,7 @@ dependencies = [ [[package]] name = "dash-context-provider" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "dpp", "drive", @@ -1400,7 +1400,7 @@ dependencies = [ [[package]] name = "dash-platform-balance-checker" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "anyhow", "clap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "dash-platform-macros" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "heck 0.5.0", "quote", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "dash-sdk" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "arc-swap", "assert_matches", @@ -1594,7 +1594,7 @@ dependencies = [ [[package]] name = "dashpay-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "platform-value", "platform-version", @@ -1604,7 +1604,7 @@ dependencies = [ [[package]] name = "data-contracts" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "dashpay-contract", "dpns-contract", @@ -1753,7 +1753,7 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dpns-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "platform-value", "platform-version", @@ -1763,7 +1763,7 @@ dependencies = [ [[package]] name = "dpp" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "anyhow", "assert_matches", @@ -1820,7 +1820,7 @@ dependencies = [ [[package]] name = "drive" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "arc-swap", "assert_matches", @@ -1861,7 +1861,7 @@ dependencies = [ [[package]] name = "drive-abci" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "arc-swap", "assert_matches", @@ -1916,7 +1916,7 @@ dependencies = [ [[package]] name = "drive-proof-verifier" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "bincode 2.0.0-rc.3", "dapi-grpc", @@ -2169,7 +2169,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "feature-flags-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "platform-value", "platform-version", @@ -3342,7 +3342,7 @@ dependencies = [ [[package]] name = "json-schema-compatibility-validator" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "assert_matches", "json-patch", @@ -3461,7 +3461,7 @@ dependencies = [ [[package]] name = "keyword-search-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "base58", "platform-value", @@ -3608,7 +3608,7 @@ dependencies = [ [[package]] name = "masternode-reward-shares-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "platform-value", "platform-version", @@ -4276,7 +4276,7 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform-serialization" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "bincode 2.0.0-rc.3", "platform-version", @@ -4284,7 +4284,7 @@ dependencies = [ [[package]] name = "platform-serialization-derive" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "proc-macro2", "quote", @@ -4294,7 +4294,7 @@ dependencies = [ [[package]] name = "platform-value" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -4313,7 +4313,7 @@ dependencies = [ [[package]] name = "platform-value-convertible" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "quote", "syn 2.0.111", @@ -4321,7 +4321,7 @@ dependencies = [ [[package]] name = "platform-version" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "bincode 2.0.0-rc.3", "grovedb-version", @@ -4332,7 +4332,7 @@ dependencies = [ [[package]] name = "platform-versioning" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "proc-macro2", "quote", @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "platform-wallet" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "dashcore", "dpp", @@ -5106,7 +5106,7 @@ dependencies = [ [[package]] name = "rs-dapi" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "async-trait", "axum 0.8.8", @@ -5155,7 +5155,7 @@ dependencies = [ [[package]] name = "rs-dapi-client" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "backon", "chrono", @@ -5180,7 +5180,7 @@ dependencies = [ [[package]] name = "rs-dash-event-bus" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "metrics", "tokio", @@ -5189,7 +5189,7 @@ dependencies = [ [[package]] name = "rs-sdk-ffi" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "bincode 2.0.0-rc.3", "bs58", @@ -5218,7 +5218,7 @@ dependencies = [ [[package]] name = "rs-sdk-trusted-context-provider" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "arc-swap", "dash-context-provider", @@ -5884,7 +5884,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-signer" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "strategy-tests" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "bincode 2.0.0-rc.3", "dpp", @@ -6374,7 +6374,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "token-history-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "platform-value", "platform-version", @@ -7117,7 +7117,7 @@ dependencies = [ [[package]] name = "wallet-utils-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "platform-value", "platform-version", @@ -7249,7 +7249,7 @@ dependencies = [ [[package]] name = "wasm-dpp" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "anyhow", "async-trait", @@ -7273,7 +7273,7 @@ dependencies = [ [[package]] name = "wasm-dpp2" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "anyhow", "bincode 2.0.0-rc.3", @@ -7290,7 +7290,7 @@ dependencies = [ [[package]] name = "wasm-drive-verify" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -7323,7 +7323,7 @@ dependencies = [ [[package]] name = "wasm-sdk" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "base64 0.22.1", "bip39", @@ -7783,7 +7783,7 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "withdrawals-contract" -version = "3.0.0" +version = "3.0.1-hotfix.2" dependencies = [ "num_enum 0.5.11", "platform-value", diff --git a/Cargo.toml b/Cargo.toml index ee5ec101e15..bbe18704dd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,5 +45,5 @@ members = [ [workspace.package] -version = "3.0.0" +version = "3.0.1-hotfix.2" rust-version = "1.92" diff --git a/package.json b/package.json index 5b595416e03..aed102026c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/platform", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "private": true, "scripts": { "setup": "yarn install && yarn run build && yarn run configure", diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index 13c70a9f87b..9a925042f6c 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/bench-suite", "private": true, - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Dash Platform benchmark tool", "scripts": { "bench": "node ./bin/bench.js", diff --git a/packages/dapi-grpc/package.json b/packages/dapi-grpc/package.json index 1776f57af6f..4821051b859 100644 --- a/packages/dapi-grpc/package.json +++ b/packages/dapi-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-grpc", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "DAPI GRPC definition file and generated clients", "browser": "browser.js", "main": "node.js", diff --git a/packages/dapi/package.json b/packages/dapi/package.json index 232a586e90a..a2a95017b97 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/dapi", "private": true, - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "A decentralized API for the Dash network", "scripts": { "api": "node scripts/api.js", diff --git a/packages/dash-spv/package.json b/packages/dash-spv/package.json index bfd91054213..0e1f6b66461 100644 --- a/packages/dash-spv/package.json +++ b/packages/dash-spv/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dash-spv", - "version": "4.0.0", + "version": "4.0.1-hotfix.2", "description": "Repository containing SPV functions used by @dashevo", "main": "index.js", "scripts": { diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index bd77ad51abb..98c5dc62df7 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -1,6 +1,6 @@ { "name": "dashmate", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Distribution package for Dash node installation", "scripts": { "lint": "eslint .", diff --git a/packages/dashmate/src/doctor/analyse/analyseConfigFactory.js b/packages/dashmate/src/doctor/analyse/analyseConfigFactory.js index 92458dd62fb..80007be0129 100644 --- a/packages/dashmate/src/doctor/analyse/analyseConfigFactory.js +++ b/packages/dashmate/src/doctor/analyse/analyseConfigFactory.js @@ -1,6 +1,7 @@ import chalk from 'chalk'; import { NETWORK_LOCAL, NETWORK_MAINNET } from '../../constants.js'; -import { ERRORS } from '../../ssl/zerossl/validateZeroSslCertificateFactory.js'; +import { ERRORS as LETSENCRYPT_ERRORS } from '../../ssl/letsencrypt/validateLetsEncryptCertificateFactory.js'; +import { ERRORS as ZEROSSL_ERRORS } from '../../ssl/zerossl/validateZeroSslCertificateFactory.js'; import { SEVERITY } from '../Prescription.js'; import Problem from '../Problem.js'; @@ -82,51 +83,80 @@ Private key file path: {bold.cyanBright ${ssl?.data?.privateFilePath}} Or use ZeroSSL https://docs.dash.org/en/stable/masternodes/dashmate.html#ssl-certificate`, }, // ZeroSSL validation errors - [ERRORS.API_KEY_IS_NOT_SET]: { + [ZEROSSL_ERRORS.API_KEY_IS_NOT_SET]: { description: 'ZeroSSL API key is not set.', solution: chalk`Please obtain your API key from {underline.cyanBright https://app.zerossl.com/developer} And then update your configuration with {block.cyanBright dashmate config set platform.gateway.ssl.providerConfigs.zerossl.apiKey [KEY]}`, }, - [ERRORS.EXTERNAL_IP_IS_NOT_SET]: { + [ZEROSSL_ERRORS.EXTERNAL_IP_IS_NOT_SET]: { description: 'External IP is not set.', solution: chalk`Please update your configuration to include your external IP using {block.cyanBright dashmate config set externalIp [IP]}`, }, - [ERRORS.CERTIFICATE_ID_IS_NOT_SET]: { + [ZEROSSL_ERRORS.CERTIFICATE_ID_IS_NOT_SET]: { description: 'ZeroSSL certificate is not configured', solution: chalk`Please run {bold.cyanBright dashmate ssl obtain} to get a new certificate`, }, - [ERRORS.PRIVATE_KEY_IS_NOT_PRESENT]: { + [ZEROSSL_ERRORS.PRIVATE_KEY_IS_NOT_PRESENT]: { description: chalk`ZeroSSL private key file not found in ${ssl?.data?.privateKeyFilePath}.`, solution: chalk`Please regenerate the certificate using {bold.cyanBright dashmate ssl obtain --force} and revoke the previous certificate in the ZeroSSL dashboard`, }, - [ERRORS.EXTERNAL_IP_MISMATCH]: { + [ZEROSSL_ERRORS.EXTERNAL_IP_MISMATCH]: { description: chalk`ZeroSSL IP ${ssl?.data?.certificate.common_name} does not match external IP ${ssl?.data?.externalIp}.`, solution: chalk`Please regenerate the certificate using {bold.cyanBright dashmate ssl obtain --force} and revoke the previous certificate in the ZeroSSL dashboard`, }, - [ERRORS.CSR_FILE_IS_NOT_PRESENT]: { + [ZEROSSL_ERRORS.CSR_FILE_IS_NOT_PRESENT]: { description: chalk`ZeroSSL certificate request file not found in ${ssl?.data?.csrFilePath}. This makes auto-renewal impossible.`, solution: chalk`If you need auto renew, please regenerate the certificate using {bold.cyanBright dashmate ssl obtain --force} and revoke the previous certificate in the ZeroSSL dashboard`, }, - [ERRORS.CERTIFICATE_EXPIRES_SOON]: { + [ZEROSSL_ERRORS.CERTIFICATE_EXPIRES_SOON]: { description: chalk`ZeroSSL certificate expires at ${ssl?.data?.certificate.expires}.`, solution: chalk`Please run {bold.cyanBright dashmate ssl obtain} to get a new one`, }, - [ERRORS.CERTIFICATE_IS_NOT_VALIDATED]: { + [ZEROSSL_ERRORS.CERTIFICATE_IS_NOT_VALIDATED]: { description: chalk`ZeroSSL certificate is not approved.`, solution: chalk`Please run {bold.cyanBright dashmate ssl obtain} to confirm certificate`, }, - [ERRORS.CERTIFICATE_IS_NOT_VALID]: { + [ZEROSSL_ERRORS.CERTIFICATE_IS_NOT_VALID]: { description: chalk`ZeroSSL certificate is not valid.`, solution: chalk`Please run {bold.cyanBright dashmate ssl zerossl obtain} to get a new one.`, }, - [ERRORS.ZERO_SSL_API_ERROR]: { + [ZEROSSL_ERRORS.ZERO_SSL_API_ERROR]: { description: ssl?.data?.error?.message, solution: chalk`Please contact ZeroSSL support if needed.`, }, + // Let's Encrypt validation errors + [LETSENCRYPT_ERRORS.EMAIL_IS_NOT_SET]: { + description: 'Let\'s Encrypt email is not set.', + solution: chalk`Please update your configuration with {bold.cyanBright dashmate config set platform.gateway.ssl.providerConfigs.letsencrypt.email [EMAIL]}`, + }, + [LETSENCRYPT_ERRORS.EXTERNAL_IP_IS_NOT_SET]: { + description: 'External IP is not set.', + solution: chalk`Please update your configuration to include your external IP using {bold.cyanBright dashmate config set externalIp [IP]}`, + }, + [LETSENCRYPT_ERRORS.CERTIFICATE_NOT_FOUND]: { + description: 'Let\'s Encrypt certificate is not configured', + solution: chalk`Please run {bold.cyanBright dashmate ssl obtain --provider=letsencrypt} to get a new certificate`, + }, + [LETSENCRYPT_ERRORS.PRIVATE_KEY_NOT_FOUND]: { + description: chalk`Let's Encrypt private key file not found.`, + solution: chalk`Please regenerate the certificate using {bold.cyanBright dashmate ssl obtain --provider=letsencrypt --force}`, + }, + [LETSENCRYPT_ERRORS.CERTIFICATE_IP_MISMATCH]: { + description: chalk`Let's Encrypt certificate does not match external IP ${ssl?.data?.externalIp}.`, + solution: chalk`Please regenerate the certificate using {bold.cyanBright dashmate ssl obtain --provider=letsencrypt --force}`, + }, + [LETSENCRYPT_ERRORS.CERTIFICATE_EXPIRES_SOON]: { + description: chalk`Let's Encrypt certificate expires at ${ssl?.data?.certificate?.expires}.`, + solution: chalk`Please run {bold.cyanBright dashmate ssl obtain --provider=letsencrypt} to renew`, + }, + [LETSENCRYPT_ERRORS.CERTIFICATE_NOT_VALID]: { + description: chalk`Let's Encrypt certificate is not valid.`, + solution: chalk`Please run {bold.cyanBright dashmate ssl obtain --provider=letsencrypt --force} to get a new one.`, + }, }[ssl.error] ?? {}; if (description) { diff --git a/packages/dashmate/src/listr/tasks/doctor/collectSamplesTaskFactory.js b/packages/dashmate/src/listr/tasks/doctor/collectSamplesTaskFactory.js index b36def7246e..46d8f4fd7ad 100644 --- a/packages/dashmate/src/listr/tasks/doctor/collectSamplesTaskFactory.js +++ b/packages/dashmate/src/listr/tasks/doctor/collectSamplesTaskFactory.js @@ -5,6 +5,7 @@ import process from 'process'; import si from 'systeminformation'; import obfuscateConfig from '../../../config/obfuscateConfig.js'; import { DASHMATE_VERSION } from '../../../constants.js'; +import LegoCertificate from '../../../ssl/letsencrypt/LegoCertificate.js'; import Certificate from '../../../ssl/zerossl/Certificate.js'; import providers from '../../../status/providers.js'; import hideString from '../../../util/hideString.js'; @@ -35,6 +36,7 @@ async function fetchTextOrError(url) { * @param {getOperatingSystemInfo} getOperatingSystemInfo * @param {HomeDir} homeDir * @param {validateZeroSslCertificate} validateZeroSslCertificate + * @param {validateLetsEncryptCertificate} validateLetsEncryptCertificate * @return {collectSamplesTask} */ export default function collectSamplesTaskFactory( @@ -46,6 +48,7 @@ export default function collectSamplesTaskFactory( getOperatingSystemInfo, homeDir, validateZeroSslCertificate, + validateLetsEncryptCertificate, ) { /** * @typedef {function} collectSamplesTask @@ -116,6 +119,27 @@ export default function collectSamplesTaskFactory( return; } + case 'letsencrypt': { + const { + error, + data, + } = await validateLetsEncryptCertificate( + config, + LegoCertificate.EXPIRATION_LIMIT_DAYS, + ); + + obfuscateObjectRecursive(data, (_field, value) => (typeof value === 'string' ? value.replaceAll( + process.env.USER, + hideString(process.env.USER), + ) : value)); + + ctx.samples.setServiceInfo('gateway', 'ssl', { + error, + data, + }); + + return; + } case 'file': { // SSL certificate const certificatesDir = homeDir.joinPath( diff --git a/packages/dashmate/src/listr/tasks/ssl/letsencrypt/obtainLetsEncryptCertificateTaskFactory.js b/packages/dashmate/src/listr/tasks/ssl/letsencrypt/obtainLetsEncryptCertificateTaskFactory.js index 004e45de7d9..0e33318edd7 100644 --- a/packages/dashmate/src/listr/tasks/ssl/letsencrypt/obtainLetsEncryptCertificateTaskFactory.js +++ b/packages/dashmate/src/listr/tasks/ssl/letsencrypt/obtainLetsEncryptCertificateTaskFactory.js @@ -36,6 +36,39 @@ export default function obtainLetsEncryptCertificateTaskFactory( */ function obtainLetsEncryptCertificateTask(config) { return new Listr([ + { + title: 'Initialize configuration', + task: async (ctx) => { + // Always load config values (needed even when --force is used) + ctx.email = config.get('platform.gateway.ssl.providerConfigs.letsencrypt.email'); + ctx.externalIp = config.get('externalIp'); + ctx.legoDir = homeDir.joinPath(config.getName(), 'platform', 'gateway', 'lego'); + ctx.sslConfigDir = homeDir.joinPath(config.getName(), 'platform', 'gateway', 'ssl'); + + if (!ctx.email) { + throw new Error("Let's Encrypt email is not set. Please set it in the config file"); + } + + if (!ctx.externalIp) { + throw new Error('External IP is not set. Please set it in the config file'); + } + + // Ensure lego directories exist + fs.mkdirSync(ctx.legoDir, { recursive: true }); + fs.mkdirSync(path.join(ctx.legoDir, 'certificates'), { recursive: true }); + fs.mkdirSync(path.join(ctx.legoDir, 'accounts'), { recursive: true }); + + // Set paths + ctx.legoCertPath = path.join(ctx.legoDir, 'certificates', `${ctx.externalIp}.crt`); + ctx.legoKeyPath = path.join(ctx.legoDir, 'certificates', `${ctx.externalIp}.key`); + + // When force is used, skip validation and obtain new certificate + if (ctx.force) { + ctx.certificateValid = false; + ctx.isRenewal = false; + } + }, + }, { title: 'Check if certificate already exists and is valid', skip: (ctx) => ctx.force, @@ -43,11 +76,12 @@ export default function obtainLetsEncryptCertificateTaskFactory( const expirationDays = ctx.expirationDays ?? LegoCertificate.EXPIRATION_LIMIT_DAYS; const { error, data } = await validateLetsEncryptCertificate(config, expirationDays); - Object.assign(ctx, data); - - // Ensure lego directory exists - fs.mkdirSync(ctx.legoDir, { recursive: true }); - fs.mkdirSync(path.join(ctx.legoDir, 'certificates'), { recursive: true }); + // Merge validation data (but don't overwrite already-set values) + Object.keys(data).forEach((key) => { + if (ctx[key] === undefined) { + ctx[key] = data[key]; + } + }); switch (error) { case undefined: @@ -109,6 +143,7 @@ export default function obtainLetsEncryptCertificateTaskFactory( // Build lego command arguments // --disable-cn is needed for IP address certificates + // --key-type rsa2048 is needed because node-forge doesn't support ECDSA const legoArgs = [ '--server=https://acme-v02.api.letsencrypt.org/directory', '--email', ctx.email, @@ -117,15 +152,15 @@ export default function obtainLetsEncryptCertificateTaskFactory( '--http.port', ':80', '--domains', ctx.externalIp, '--disable-cn', + '--key-type', 'rsa2048', '--path', '/data', command, ]; - // Add profile for initial run - if (!ctx.isRenewal) { - legoArgs.push('--profile', 'shortlived'); - } else { - // For renewal, renew if within 30 days of expiry (default) + // shortlived profile is required for IP address certificates + legoArgs.push('--profile', 'shortlived'); + + if (ctx.isRenewal) { legoArgs.push('--days', '30'); } diff --git a/packages/dashpay-contract/package.json b/packages/dashpay-contract/package.json index 630f5ebab03..212a647ce1f 100644 --- a/packages/dashpay-contract/package.json +++ b/packages/dashpay-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dashpay-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Reference contract of the DashPay DPA on Dash Evolution", "scripts": { "lint": "eslint .", diff --git a/packages/dpns-contract/package.json b/packages/dpns-contract/package.json index ef7e04ebf62..686566615d5 100644 --- a/packages/dpns-contract/package.json +++ b/packages/dpns-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpns-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "A contract and helper scripts for DPNS DApp", "scripts": { "lint": "eslint .", diff --git a/packages/feature-flags-contract/package.json b/packages/feature-flags-contract/package.json index a23e2072a28..fd12b822e87 100644 --- a/packages/feature-flags-contract/package.json +++ b/packages/feature-flags-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/feature-flags-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Data Contract to store Dash Platform feature flags", "scripts": { "build": "", diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index 764a5ef4359..fdd95ba2bc2 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-client", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Client library used to access Dash DAPI endpoints", "main": "lib/index.js", "contributors": [ diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index 127e3eaeb8a..6b455650909 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -1,6 +1,6 @@ { "name": "dash", - "version": "6.0.0", + "version": "6.0.1-hotfix.2", "description": "Dash library for JavaScript/TypeScript ecosystem (Wallet, DAPI, Primitives, BLS, ...)", "main": "build/index.js", "unpkg": "dist/dash.min.js", diff --git a/packages/js-evo-sdk/package.json b/packages/js-evo-sdk/package.json index c6528c38993..1d96227ae5c 100644 --- a/packages/js-evo-sdk/package.json +++ b/packages/js-evo-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/evo-sdk", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "type": "module", "main": "./dist/evo-sdk.module.js", "types": "./dist/sdk.d.ts", diff --git a/packages/js-grpc-common/package.json b/packages/js-grpc-common/package.json index 8151e7f4c54..7e18a2a3122 100644 --- a/packages/js-grpc-common/package.json +++ b/packages/js-grpc-common/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/grpc-common", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Common GRPC library", "main": "index.js", "scripts": { diff --git a/packages/keyword-search-contract/package.json b/packages/keyword-search-contract/package.json index 647483bee2c..f1f15f7dd0c 100644 --- a/packages/keyword-search-contract/package.json +++ b/packages/keyword-search-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/keyword-search-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "A contract that allows searching for contracts", "scripts": { "lint": "eslint .", diff --git a/packages/masternode-reward-shares-contract/package.json b/packages/masternode-reward-shares-contract/package.json index 811bae73afe..8cb0f20be52 100644 --- a/packages/masternode-reward-shares-contract/package.json +++ b/packages/masternode-reward-shares-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/masternode-reward-shares-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "A contract and helper scripts for reward sharing", "scripts": { "lint": "eslint .", diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index bb09c35a236..3ccd184384b 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/platform-test-suite", "private": true, - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Dash Network end-to-end tests", "scripts": { "test": "yarn exec bin/test.sh", diff --git a/packages/token-history-contract/package.json b/packages/token-history-contract/package.json index 0ff8a1fc1c0..1bd720af246 100644 --- a/packages/token-history-contract/package.json +++ b/packages/token-history-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/token-history-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "The token history contract", "scripts": { "lint": "eslint .", diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index b058bce93a6..99c8f1860bb 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-lib", - "version": "10.0.0", + "version": "10.0.1-hotfix.2", "description": "Light wallet library for Dash", "main": "src/index.js", "unpkg": "dist/wallet-lib.min.js", diff --git a/packages/wallet-utils-contract/package.json b/packages/wallet-utils-contract/package.json index 531474d3f25..69a5c4ffc76 100644 --- a/packages/wallet-utils-contract/package.json +++ b/packages/wallet-utils-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-utils-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "A contract and helper scripts for Wallet DApp", "scripts": { "lint": "eslint .", diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index 4a0bfb9a7e1..b4c2e863929 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "The JavaScript implementation of the Dash Platform Protocol", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/wasm-dpp2/package.json b/packages/wasm-dpp2/package.json index a6ebe37cc4f..9b5fb490e9c 100644 --- a/packages/wasm-dpp2/package.json +++ b/packages/wasm-dpp2/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp2", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "type": "module", "main": "./dist/dpp.js", "types": "./dist/dpp.d.ts", diff --git a/packages/wasm-drive-verify/package.json b/packages/wasm-drive-verify/package.json index b3a9966f7a1..94951334f62 100644 --- a/packages/wasm-drive-verify/package.json +++ b/packages/wasm-drive-verify/package.json @@ -3,7 +3,7 @@ "collaborators": [ "Dash Core Group " ], - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "license": "MIT", "description": "WASM bindings for Drive verify functions", "repository": { diff --git a/packages/wasm-sdk/package.json b/packages/wasm-sdk/package.json index a81af30aaab..570c6c7fa08 100644 --- a/packages/wasm-sdk/package.json +++ b/packages/wasm-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-sdk", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "type": "module", "main": "./dist/sdk.js", "types": "./dist/sdk.d.ts", diff --git a/packages/withdrawals-contract/package.json b/packages/withdrawals-contract/package.json index 01ac9cbaa19..2cca7ed1733 100644 --- a/packages/withdrawals-contract/package.json +++ b/packages/withdrawals-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/withdrawals-contract", - "version": "3.0.0", + "version": "3.0.1-hotfix.2", "description": "Data Contract to manipulate and track withdrawals", "scripts": { "build": "", From 2eb9a0d8356120c4f32c28933cd5acd0bd6712cc Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 5 Feb 2026 13:25:30 +0700 Subject: [PATCH 2/8] chore(dashmate): upgrade to Core 23 (#3054) --- packages/dashmate/configs/defaults/getBaseConfigFactory.js | 2 +- .../dashmate/configs/getConfigFileMigrationsFactory.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/dashmate/configs/defaults/getBaseConfigFactory.js b/packages/dashmate/configs/defaults/getBaseConfigFactory.js index 3ea84fb7509..c9551ff743c 100644 --- a/packages/dashmate/configs/defaults/getBaseConfigFactory.js +++ b/packages/dashmate/configs/defaults/getBaseConfigFactory.js @@ -53,7 +53,7 @@ export default function getBaseConfigFactory() { port: 3001, }, docker: { - image: 'dashpay/dashd:22', + image: 'dashpay/dashd:23', commandArgs: [], }, p2p: { diff --git a/packages/dashmate/configs/getConfigFileMigrationsFactory.js b/packages/dashmate/configs/getConfigFileMigrationsFactory.js index c1b3bb77726..f7a6669f474 100644 --- a/packages/dashmate/configs/getConfigFileMigrationsFactory.js +++ b/packages/dashmate/configs/getConfigFileMigrationsFactory.js @@ -1398,6 +1398,13 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs) return configFile; }, + '3.0.1': (configFile) => { + Object.entries(configFile.configs) + .forEach(([, options]) => { + options.core.docker.image = 'dashpay/dashd:23'; + }); + return configFile; + }, }; } From 707a66415b9178f62bb66b3cd1ddef61fda02ab9 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 5 Feb 2026 13:27:20 +0700 Subject: [PATCH 3/8] chore(release): update changelog and bump version to 3.0.1-hotfix.3 (#3055) --- CHANGELOG.md | 12 +++ Cargo.lock | 76 +++++++++---------- Cargo.toml | 2 +- package.json | 2 +- packages/bench-suite/package.json | 2 +- packages/dapi-grpc/package.json | 2 +- packages/dapi/package.json | 2 +- packages/dash-spv/package.json | 2 +- packages/dashmate/package.json | 2 +- packages/dashpay-contract/package.json | 2 +- packages/dpns-contract/package.json | 2 +- packages/feature-flags-contract/package.json | 2 +- packages/js-dapi-client/package.json | 2 +- packages/js-dash-sdk/package.json | 2 +- packages/js-evo-sdk/package.json | 2 +- packages/js-grpc-common/package.json | 2 +- packages/keyword-search-contract/package.json | 2 +- .../package.json | 2 +- packages/platform-test-suite/package.json | 2 +- packages/token-history-contract/package.json | 2 +- packages/wallet-lib/package.json | 2 +- packages/wallet-utils-contract/package.json | 2 +- packages/wasm-dpp/package.json | 2 +- packages/wasm-dpp2/package.json | 2 +- packages/wasm-drive-verify/package.json | 2 +- packages/wasm-sdk/package.json | 2 +- packages/withdrawals-contract/package.json | 2 +- 27 files changed, 75 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65cb35b0492..acac59b0ee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +### [3.0.1-hotfix.3](https://github.com/dashpay/platform/compare/v3.0.0...v3.0.1-hotfix.3) (2026-02-05) + + +### Bug Fixes + +* **dashmate:** letsencrypt renewal and dashmate doctor fixes ([#3018](https://github.com/dashpay/platform/issues/3018)) + + +### Miscellaneous Chores + +* **dashmate:** upgrade to Core 23 ([#3054](https://github.com/dashpay/platform/issues/3054)) + ### [3.0.1-hotfix.2](https://github.com/dashpay/platform/compare/v3.0.1-hotfix.1...v3.0.1-hotfix.2) (2026-02-02) diff --git a/Cargo.lock b/Cargo.lock index ea1f91e7515..52417adf7c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,7 +856,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "check-features" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "toml 0.8.23", ] @@ -1290,7 +1290,7 @@ dependencies = [ [[package]] name = "dapi-grpc" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "dash-platform-macros", "futures-core", @@ -1378,7 +1378,7 @@ dependencies = [ [[package]] name = "dash-context-provider" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "dpp", "drive", @@ -1400,7 +1400,7 @@ dependencies = [ [[package]] name = "dash-platform-balance-checker" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "anyhow", "clap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "dash-platform-macros" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "heck 0.5.0", "quote", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "dash-sdk" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "arc-swap", "assert_matches", @@ -1594,7 +1594,7 @@ dependencies = [ [[package]] name = "dashpay-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "platform-value", "platform-version", @@ -1604,7 +1604,7 @@ dependencies = [ [[package]] name = "data-contracts" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "dashpay-contract", "dpns-contract", @@ -1753,7 +1753,7 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dpns-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "platform-value", "platform-version", @@ -1763,7 +1763,7 @@ dependencies = [ [[package]] name = "dpp" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "anyhow", "assert_matches", @@ -1820,7 +1820,7 @@ dependencies = [ [[package]] name = "drive" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "arc-swap", "assert_matches", @@ -1861,7 +1861,7 @@ dependencies = [ [[package]] name = "drive-abci" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "arc-swap", "assert_matches", @@ -1916,7 +1916,7 @@ dependencies = [ [[package]] name = "drive-proof-verifier" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "bincode 2.0.0-rc.3", "dapi-grpc", @@ -2169,7 +2169,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "feature-flags-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "platform-value", "platform-version", @@ -3342,7 +3342,7 @@ dependencies = [ [[package]] name = "json-schema-compatibility-validator" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "assert_matches", "json-patch", @@ -3461,7 +3461,7 @@ dependencies = [ [[package]] name = "keyword-search-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "base58", "platform-value", @@ -3608,7 +3608,7 @@ dependencies = [ [[package]] name = "masternode-reward-shares-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "platform-value", "platform-version", @@ -4276,7 +4276,7 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform-serialization" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "bincode 2.0.0-rc.3", "platform-version", @@ -4284,7 +4284,7 @@ dependencies = [ [[package]] name = "platform-serialization-derive" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "proc-macro2", "quote", @@ -4294,7 +4294,7 @@ dependencies = [ [[package]] name = "platform-value" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -4313,7 +4313,7 @@ dependencies = [ [[package]] name = "platform-value-convertible" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "quote", "syn 2.0.111", @@ -4321,7 +4321,7 @@ dependencies = [ [[package]] name = "platform-version" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "bincode 2.0.0-rc.3", "grovedb-version", @@ -4332,7 +4332,7 @@ dependencies = [ [[package]] name = "platform-versioning" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "proc-macro2", "quote", @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "platform-wallet" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "dashcore", "dpp", @@ -5106,7 +5106,7 @@ dependencies = [ [[package]] name = "rs-dapi" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "async-trait", "axum 0.8.8", @@ -5155,7 +5155,7 @@ dependencies = [ [[package]] name = "rs-dapi-client" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "backon", "chrono", @@ -5180,7 +5180,7 @@ dependencies = [ [[package]] name = "rs-dash-event-bus" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "metrics", "tokio", @@ -5189,7 +5189,7 @@ dependencies = [ [[package]] name = "rs-sdk-ffi" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "bincode 2.0.0-rc.3", "bs58", @@ -5218,7 +5218,7 @@ dependencies = [ [[package]] name = "rs-sdk-trusted-context-provider" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "arc-swap", "dash-context-provider", @@ -5884,7 +5884,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-signer" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "strategy-tests" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "bincode 2.0.0-rc.3", "dpp", @@ -6374,7 +6374,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "token-history-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "platform-value", "platform-version", @@ -7117,7 +7117,7 @@ dependencies = [ [[package]] name = "wallet-utils-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "platform-value", "platform-version", @@ -7249,7 +7249,7 @@ dependencies = [ [[package]] name = "wasm-dpp" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "anyhow", "async-trait", @@ -7273,7 +7273,7 @@ dependencies = [ [[package]] name = "wasm-dpp2" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "anyhow", "bincode 2.0.0-rc.3", @@ -7290,7 +7290,7 @@ dependencies = [ [[package]] name = "wasm-drive-verify" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -7323,7 +7323,7 @@ dependencies = [ [[package]] name = "wasm-sdk" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "base64 0.22.1", "bip39", @@ -7783,7 +7783,7 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "withdrawals-contract" -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" dependencies = [ "num_enum 0.5.11", "platform-value", diff --git a/Cargo.toml b/Cargo.toml index bbe18704dd4..62e49f87411 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,5 +45,5 @@ members = [ [workspace.package] -version = "3.0.1-hotfix.2" +version = "3.0.1-hotfix.3" rust-version = "1.92" diff --git a/package.json b/package.json index aed102026c2..cff16d6fc0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/platform", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "private": true, "scripts": { "setup": "yarn install && yarn run build && yarn run configure", diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index 9a925042f6c..4e2cfbcb667 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/bench-suite", "private": true, - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Dash Platform benchmark tool", "scripts": { "bench": "node ./bin/bench.js", diff --git a/packages/dapi-grpc/package.json b/packages/dapi-grpc/package.json index 4821051b859..7d108a53287 100644 --- a/packages/dapi-grpc/package.json +++ b/packages/dapi-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-grpc", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "DAPI GRPC definition file and generated clients", "browser": "browser.js", "main": "node.js", diff --git a/packages/dapi/package.json b/packages/dapi/package.json index a2a95017b97..4fd13dd371b 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/dapi", "private": true, - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "A decentralized API for the Dash network", "scripts": { "api": "node scripts/api.js", diff --git a/packages/dash-spv/package.json b/packages/dash-spv/package.json index 0e1f6b66461..361b1ce26fb 100644 --- a/packages/dash-spv/package.json +++ b/packages/dash-spv/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dash-spv", - "version": "4.0.1-hotfix.2", + "version": "4.0.1-hotfix.3", "description": "Repository containing SPV functions used by @dashevo", "main": "index.js", "scripts": { diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 98c5dc62df7..05d48c61d71 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -1,6 +1,6 @@ { "name": "dashmate", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Distribution package for Dash node installation", "scripts": { "lint": "eslint .", diff --git a/packages/dashpay-contract/package.json b/packages/dashpay-contract/package.json index 212a647ce1f..d3a86b99237 100644 --- a/packages/dashpay-contract/package.json +++ b/packages/dashpay-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dashpay-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Reference contract of the DashPay DPA on Dash Evolution", "scripts": { "lint": "eslint .", diff --git a/packages/dpns-contract/package.json b/packages/dpns-contract/package.json index 686566615d5..9cfbb1a340e 100644 --- a/packages/dpns-contract/package.json +++ b/packages/dpns-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpns-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "A contract and helper scripts for DPNS DApp", "scripts": { "lint": "eslint .", diff --git a/packages/feature-flags-contract/package.json b/packages/feature-flags-contract/package.json index fd12b822e87..0e8af066988 100644 --- a/packages/feature-flags-contract/package.json +++ b/packages/feature-flags-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/feature-flags-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Data Contract to store Dash Platform feature flags", "scripts": { "build": "", diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index fdd95ba2bc2..efd5808a9fe 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-client", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Client library used to access Dash DAPI endpoints", "main": "lib/index.js", "contributors": [ diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index 6b455650909..8470d6f749c 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -1,6 +1,6 @@ { "name": "dash", - "version": "6.0.1-hotfix.2", + "version": "6.0.1-hotfix.3", "description": "Dash library for JavaScript/TypeScript ecosystem (Wallet, DAPI, Primitives, BLS, ...)", "main": "build/index.js", "unpkg": "dist/dash.min.js", diff --git a/packages/js-evo-sdk/package.json b/packages/js-evo-sdk/package.json index 1d96227ae5c..dacf6588e22 100644 --- a/packages/js-evo-sdk/package.json +++ b/packages/js-evo-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/evo-sdk", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "type": "module", "main": "./dist/evo-sdk.module.js", "types": "./dist/sdk.d.ts", diff --git a/packages/js-grpc-common/package.json b/packages/js-grpc-common/package.json index 7e18a2a3122..800216df697 100644 --- a/packages/js-grpc-common/package.json +++ b/packages/js-grpc-common/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/grpc-common", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Common GRPC library", "main": "index.js", "scripts": { diff --git a/packages/keyword-search-contract/package.json b/packages/keyword-search-contract/package.json index f1f15f7dd0c..04dcfd33503 100644 --- a/packages/keyword-search-contract/package.json +++ b/packages/keyword-search-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/keyword-search-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "A contract that allows searching for contracts", "scripts": { "lint": "eslint .", diff --git a/packages/masternode-reward-shares-contract/package.json b/packages/masternode-reward-shares-contract/package.json index 8cb0f20be52..a23a910a931 100644 --- a/packages/masternode-reward-shares-contract/package.json +++ b/packages/masternode-reward-shares-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/masternode-reward-shares-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "A contract and helper scripts for reward sharing", "scripts": { "lint": "eslint .", diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index 3ccd184384b..7296322e954 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/platform-test-suite", "private": true, - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Dash Network end-to-end tests", "scripts": { "test": "yarn exec bin/test.sh", diff --git a/packages/token-history-contract/package.json b/packages/token-history-contract/package.json index 1bd720af246..18fa946d929 100644 --- a/packages/token-history-contract/package.json +++ b/packages/token-history-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/token-history-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "The token history contract", "scripts": { "lint": "eslint .", diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index 99c8f1860bb..774dae9a36b 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-lib", - "version": "10.0.1-hotfix.2", + "version": "10.0.1-hotfix.3", "description": "Light wallet library for Dash", "main": "src/index.js", "unpkg": "dist/wallet-lib.min.js", diff --git a/packages/wallet-utils-contract/package.json b/packages/wallet-utils-contract/package.json index 69a5c4ffc76..4fe60109e55 100644 --- a/packages/wallet-utils-contract/package.json +++ b/packages/wallet-utils-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-utils-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "A contract and helper scripts for Wallet DApp", "scripts": { "lint": "eslint .", diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index b4c2e863929..eb0f4c0e0e7 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "The JavaScript implementation of the Dash Platform Protocol", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/wasm-dpp2/package.json b/packages/wasm-dpp2/package.json index 9b5fb490e9c..fd3304e10d8 100644 --- a/packages/wasm-dpp2/package.json +++ b/packages/wasm-dpp2/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp2", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "type": "module", "main": "./dist/dpp.js", "types": "./dist/dpp.d.ts", diff --git a/packages/wasm-drive-verify/package.json b/packages/wasm-drive-verify/package.json index 94951334f62..7d59e150346 100644 --- a/packages/wasm-drive-verify/package.json +++ b/packages/wasm-drive-verify/package.json @@ -3,7 +3,7 @@ "collaborators": [ "Dash Core Group " ], - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "license": "MIT", "description": "WASM bindings for Drive verify functions", "repository": { diff --git a/packages/wasm-sdk/package.json b/packages/wasm-sdk/package.json index 570c6c7fa08..6a01b753842 100644 --- a/packages/wasm-sdk/package.json +++ b/packages/wasm-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-sdk", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "type": "module", "main": "./dist/sdk.js", "types": "./dist/sdk.d.ts", diff --git a/packages/withdrawals-contract/package.json b/packages/withdrawals-contract/package.json index 2cca7ed1733..5f894140955 100644 --- a/packages/withdrawals-contract/package.json +++ b/packages/withdrawals-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/withdrawals-contract", - "version": "3.0.1-hotfix.2", + "version": "3.0.1-hotfix.3", "description": "Data Contract to manipulate and track withdrawals", "scripts": { "build": "", From d12124e2781714b49f8e35cc1f801dab735f86a6 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Wed, 4 Feb 2026 21:25:05 -0800 Subject: [PATCH 4/8] fix(platform)!: 3.0 audit report fixes (#3053) --- .../v0/mod.rs | 45 +- packages/rs-dpp/src/address_funds/witness.rs | 130 ++++ .../src/errors/consensus/basic/basic_error.rs | 4 + .../consensus/basic/state_transition/mod.rs | 2 + .../withdrawal_below_min_amount_error.rs | 52 ++ packages/rs-dpp/src/errors/consensus/codes.rs | 1 + .../v0/state_transition_validation.rs | 50 +- .../state_transition_witness_validation.rs | 22 +- .../rs-dpp/src/util/is_fibonacci_number.rs | 20 - .../src/util/is_non_zero_fibonacci_number.rs | 129 ++++ packages/rs-dpp/src/util/mod.rs | 2 +- .../address_credit_withdrawal/tests.rs | 454 +++++++++++- .../address_funding_from_asset_lock/tests.rs | 667 ++++++++++++++++++ .../address_funds_transfer/tests.rs | 518 ++++++++++++-- .../identity_create_from_addresses/mod.rs | 2 + .../identity_create_from_addresses/tests.rs | 440 +++++++++++- .../tests.rs | 192 +++++ .../structure/v1/mod.rs | 4 +- .../identity_top_up_from_addresses/tests.rs | 179 ++++- .../set_balance_to_address/v0/mod.rs | 8 + ...ress_funding_from_asset_lock_transition.rs | 11 +- .../v0/transformer.rs | 28 +- .../address_funding_from_asset_lock/mod.rs | 18 +- .../address_funding_from_asset_lock/v0/mod.rs | 2 + .../v0/transformer.rs | 5 + .../v0/transformer.rs | 30 +- .../v0/transformer.rs | 30 +- .../src/errors/consensus/consensus_error.rs | 5 +- 28 files changed, 2881 insertions(+), 169 deletions(-) create mode 100644 packages/rs-dpp/src/errors/consensus/basic/state_transition/withdrawal_below_min_amount_error.rs delete mode 100644 packages/rs-dpp/src/util/is_fibonacci_number.rs create mode 100644 packages/rs-dpp/src/util/is_non_zero_fibonacci_number.rs diff --git a/packages/rs-dpp/src/address_funds/fee_strategy/deduct_fee_from_inputs_and_outputs/v0/mod.rs b/packages/rs-dpp/src/address_funds/fee_strategy/deduct_fee_from_inputs_and_outputs/v0/mod.rs index e52f2623a06..ed975814d07 100644 --- a/packages/rs-dpp/src/address_funds/fee_strategy/deduct_fee_from_inputs_and_outputs/v0/mod.rs +++ b/packages/rs-dpp/src/address_funds/fee_strategy/deduct_fee_from_inputs_and_outputs/v0/mod.rs @@ -24,6 +24,11 @@ pub fn deduct_fee_from_outputs_or_remaining_balance_of_inputs_v0( ) -> Result { let mut remaining_fee = fee; + // Snapshot addresses before any mutations so indices remain stable. + // Without this, removing a drained entry shifts all subsequent indices. + let input_addresses: Vec = inputs.keys().copied().collect(); + let output_addresses: Vec = outputs.keys().copied().collect(); + for step in fee_strategy { if remaining_fee == 0 { break; @@ -31,30 +36,34 @@ pub fn deduct_fee_from_outputs_or_remaining_balance_of_inputs_v0( match step { AddressFundsFeeStrategyStep::DeductFromInput(index) => { - // Reduce the remaining balance of the input at the specified index - if let Some((&address, &(nonce, amount))) = inputs.iter().nth(*index as usize) { - let reduction = remaining_fee.min(amount); - let new_amount = amount - reduction; - remaining_fee -= reduction; + // Resolve the index via the original snapshot, then look up by key + if let Some(&address) = input_addresses.get(*index as usize) { + if let Some(&(nonce, amount)) = inputs.get(&address) { + let reduction = remaining_fee.min(amount); + let new_amount = amount - reduction; + remaining_fee -= reduction; - if new_amount == 0 { - inputs.remove(&address); - } else { - inputs.insert(address, (nonce, new_amount)); + if new_amount == 0 { + inputs.remove(&address); + } else { + inputs.insert(address, (nonce, new_amount)); + } } } } AddressFundsFeeStrategyStep::ReduceOutput(index) => { - // Reduce the output at the specified index - if let Some((&address, &amount)) = outputs.iter().nth(*index as usize) { - let reduction = remaining_fee.min(amount); - let new_amount = amount - reduction; - remaining_fee -= reduction; + // Resolve the index via the original snapshot, then look up by key + if let Some(&address) = output_addresses.get(*index as usize) { + if let Some(&amount) = outputs.get(&address) { + let reduction = remaining_fee.min(amount); + let new_amount = amount - reduction; + remaining_fee -= reduction; - if new_amount == 0 { - outputs.remove(&address); - } else { - outputs.insert(address, new_amount); + if new_amount == 0 { + outputs.remove(&address); + } else { + outputs.insert(address, new_amount); + } } } } diff --git a/packages/rs-dpp/src/address_funds/witness.rs b/packages/rs-dpp/src/address_funds/witness.rs index e4a057dbaa2..c13aa350cf8 100644 --- a/packages/rs-dpp/src/address_funds/witness.rs +++ b/packages/rs-dpp/src/address_funds/witness.rs @@ -6,6 +6,10 @@ use platform_value::BinaryData; #[cfg(feature = "state-transition-serde-conversion")] use serde::{Deserialize, Serialize}; +/// Maximum number of entries in a P2SH signatures vector. +/// This is 16 (max keys from OP_PUSHNUM_16) + 1 (CHECKMULTISIG dummy byte). +pub const MAX_P2SH_SIGNATURES: usize = 17; + /// The input witness data required to spend from a PlatformAddress. /// /// This enum captures the different spending patterns for P2PKH and P2SH addresses. @@ -63,6 +67,13 @@ impl Decode for AddressWitness { } 1 => { let signatures = Vec::::decode(decoder)?; + if signatures.len() > MAX_P2SH_SIGNATURES { + return Err(DecodeError::OtherString(format!( + "P2SH signatures count {} exceeds maximum {}", + signatures.len(), + MAX_P2SH_SIGNATURES, + ))); + } let redeem_script = BinaryData::decode(decoder)?; Ok(AddressWitness::P2sh { signatures, @@ -87,6 +98,13 @@ impl<'de> bincode::BorrowDecode<'de> for AddressWitness { } 1 => { let signatures = Vec::::borrow_decode(decoder)?; + if signatures.len() > MAX_P2SH_SIGNATURES { + return Err(DecodeError::OtherString(format!( + "P2SH signatures count {} exceeds maximum {}", + signatures.len(), + MAX_P2SH_SIGNATURES, + ))); + } let redeem_script = BinaryData::borrow_decode(decoder)?; Ok(AddressWitness::P2sh { signatures, @@ -188,6 +206,13 @@ impl<'de> Deserialize<'de> for AddressWitness { "p2sh" => { let signatures = signatures.ok_or_else(|| de::Error::missing_field("signatures"))?; + if signatures.len() > MAX_P2SH_SIGNATURES { + return Err(de::Error::custom(format!( + "P2SH signatures count {} exceeds maximum {}", + signatures.len(), + MAX_P2SH_SIGNATURES, + ))); + } let redeem_script = redeem_script .ok_or_else(|| de::Error::missing_field("redeemScript"))?; Ok(AddressWitness::P2sh { @@ -378,4 +403,109 @@ mod tests { assert_eq!(witness, deserialized); } + + /// AUDIT L1: Unbounded P2SH witness size during deserialization. + /// + /// The `Decode` impl for `AddressWitness::P2sh` now enforces + /// `MAX_P2SH_SIGNATURES` during deserialization. A payload with more + /// signatures than the limit is rejected with a decode error. + /// + /// Location: rs-dpp/src/address_funds/witness.rs + #[test] + fn test_p2sh_witness_rejects_excessive_signatures() { + // Create a P2SH witness with 1000 signatures — far above MAX_P2SH_SIGNATURES + let num_signatures = 1000; + let signatures: Vec = (0..num_signatures) + .map(|i| BinaryData::new(vec![0x30, 0x44, i as u8])) + .collect(); + + let witness = AddressWitness::P2sh { + signatures, + redeem_script: BinaryData::new(vec![0x52, 0xae]), + }; + + // Encode succeeds (encoding has no limit), but decode must reject + let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap(); + let result: Result<(AddressWitness, usize), _> = + bincode::decode_from_slice(&encoded, config::standard()); + + assert!( + result.is_err(), + "AUDIT L1: P2SH witness with {} signatures should be rejected during \ + deserialization. MAX_P2SH_SIGNATURES = {}.", + num_signatures, + MAX_P2SH_SIGNATURES, + ); + } + + /// AUDIT L3: No maximum length check on P2SH signatures vector. + /// + /// The deserialization now enforces `MAX_P2SH_SIGNATURES` (17). Signature + /// counts above this limit are rejected during decode. The boundary value + /// (17) is accepted, and 18+ is rejected. + /// + /// Location: rs-dpp/src/address_funds/witness.rs + #[test] + fn test_p2sh_witness_max_signatures_boundary() { + // Counts above MAX_P2SH_SIGNATURES should be rejected during decode + for count in [50, 100, 500] { + let signatures: Vec = (0..count) + .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20])) + .collect(); + + let witness = AddressWitness::P2sh { + signatures, + redeem_script: BinaryData::new(vec![0x52, 0xae]), + }; + + let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap(); + let result: Result<(AddressWitness, usize), _> = + bincode::decode_from_slice(&encoded, config::standard()); + + assert!( + result.is_err(), + "AUDIT L3: P2SH witness with {} signatures should be rejected during \ + deserialization. MAX_P2SH_SIGNATURES = {}.", + count, + MAX_P2SH_SIGNATURES, + ); + } + + // MAX_P2SH_SIGNATURES (17) should be accepted + let signatures: Vec = (0..MAX_P2SH_SIGNATURES) + .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20])) + .collect(); + + let witness = AddressWitness::P2sh { + signatures, + redeem_script: BinaryData::new(vec![0x52, 0xae]), + }; + + let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap(); + let decoded: AddressWitness = bincode::decode_from_slice(&encoded, config::standard()) + .unwrap() + .0; + + assert_eq!(witness, decoded); + + // MAX_P2SH_SIGNATURES + 1 should be rejected + let signatures: Vec = (0..MAX_P2SH_SIGNATURES + 1) + .map(|_| BinaryData::new(vec![0x30, 0x44, 0x02, 0x20])) + .collect(); + + let witness = AddressWitness::P2sh { + signatures, + redeem_script: BinaryData::new(vec![0x52, 0xae]), + }; + + let encoded = bincode::encode_to_vec(&witness, config::standard()).unwrap(); + let result: Result<(AddressWitness, usize), _> = + bincode::decode_from_slice(&encoded, config::standard()); + + assert!( + result.is_err(), + "P2SH witness with {} signatures (MAX + 1) should be rejected", + MAX_P2SH_SIGNATURES + 1, + ); + } } diff --git a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs index 08f6a5aa874..db5d518eb93 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -80,6 +80,7 @@ use crate::consensus::basic::state_transition::{ OutputsNotGreaterThanInputsError, StateTransitionMaxSizeExceededError, StateTransitionNotActiveError, TransitionNoInputsError, TransitionNoOutputsError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, WithdrawalBalanceMismatchError, + WithdrawalBelowMinAmountError, }; use crate::consensus::basic::{ IncompatibleProtocolVersionError, UnsupportedFeatureError, UnsupportedProtocolVersionError, @@ -645,6 +646,9 @@ pub enum BasicError { #[error(transparent)] WithdrawalBalanceMismatchError(WithdrawalBalanceMismatchError), + #[error(transparent)] + WithdrawalBelowMinAmountError(WithdrawalBelowMinAmountError), + #[error(transparent)] InsufficientFundingAmountError(InsufficientFundingAmountError), diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs index 7753bafef39..a7c02e2db76 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs @@ -20,6 +20,7 @@ mod transition_no_outputs_error; mod transition_over_max_inputs_error; mod transition_over_max_outputs_error; mod withdrawal_balance_mismatch_error; +mod withdrawal_below_min_amount_error; pub use fee_strategy_duplicate_error::*; pub use fee_strategy_empty_error::*; @@ -43,3 +44,4 @@ pub use transition_no_outputs_error::*; pub use transition_over_max_inputs_error::*; pub use transition_over_max_outputs_error::*; pub use withdrawal_balance_mismatch_error::*; +pub use withdrawal_below_min_amount_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/withdrawal_below_min_amount_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/withdrawal_below_min_amount_error.rs new file mode 100644 index 00000000000..7372a9f184d --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/withdrawal_below_min_amount_error.rs @@ -0,0 +1,52 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error( + "Withdrawal amount {withdrawal_amount} must be at least {min_amount} and at most {max_amount}" +)] +#[platform_serialize(unversioned)] +pub struct WithdrawalBelowMinAmountError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + withdrawal_amount: u64, + min_amount: u64, + max_amount: u64, +} + +impl WithdrawalBelowMinAmountError { + pub fn new(withdrawal_amount: u64, min_amount: u64, max_amount: u64) -> Self { + Self { + withdrawal_amount, + min_amount, + max_amount, + } + } + + pub fn withdrawal_amount(&self) -> u64 { + self.withdrawal_amount + } + + pub fn min_amount(&self) -> u64 { + self.min_amount + } + + pub fn max_amount(&self) -> u64 { + self.max_amount + } +} + +impl From for ConsensusError { + fn from(err: WithdrawalBelowMinAmountError) -> Self { + Self::BasicError(BasicError::WithdrawalBelowMinAmountError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index b25a99a6909..1758f022ac8 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -231,6 +231,7 @@ impl ErrorWithCode for BasicError { Self::InputsNotLessThanOutputsError(_) => 10815, Self::OutputAddressAlsoInputError(_) => 10816, Self::InvalidRemainderOutputCountError(_) => 10817, + Self::WithdrawalBelowMinAmountError(_) => 10818, } } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_credit_withdrawal_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_credit_withdrawal_transition/v0/state_transition_validation.rs index 1944600870b..9dcbc061782 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_credit_withdrawal_transition/v0/state_transition_validation.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/address_funds/address_credit_withdrawal_transition/v0/state_transition_validation.rs @@ -9,13 +9,15 @@ use crate::consensus::basic::state_transition::{ FeeStrategyDuplicateError, FeeStrategyEmptyError, FeeStrategyIndexOutOfBoundsError, FeeStrategyTooManyStepsError, InputBelowMinimumError, InputWitnessCountMismatchError, OutputAddressAlsoInputError, OutputBelowMinimumError, TransitionNoInputsError, - TransitionOverMaxInputsError, + TransitionOverMaxInputsError, WithdrawalBalanceMismatchError, WithdrawalBelowMinAmountError, }; use crate::consensus::basic::BasicError; use crate::state_transition::address_credit_withdrawal_transition::v0::AddressCreditWithdrawalTransitionV0; -use crate::state_transition::address_credit_withdrawal_transition::MIN_CORE_FEE_PER_BYTE; +use crate::state_transition::address_credit_withdrawal_transition::{ + MIN_CORE_FEE_PER_BYTE, MIN_WITHDRAWAL_AMOUNT, +}; use crate::state_transition::StateTransitionStructureValidation; -use crate::util::is_fibonacci_number::is_fibonacci_number; +use crate::util::is_non_zero_fibonacci_number::is_non_zero_fibonacci_number; use crate::validation::SimpleConsensusValidationResult; use crate::withdrawal::Pooling; use platform_version::version::PlatformVersion; @@ -171,9 +173,6 @@ impl StateTransitionStructureValidation for AddressCreditWithdrawalTransitionV0 } } - // Note: The withdrawal amount is implicitly input_sum - output_sum - // No explicit balance check needed here as the withdrawal amount is computed, not specified - // Validate pooling - currently we do not support pooling, so we must validate that pooling is `Never` if self.pooling != Pooling::Never { return SimpleConsensusValidationResult::new_with_error( @@ -183,7 +182,7 @@ impl StateTransitionStructureValidation for AddressCreditWithdrawalTransitionV0 } // Validate core_fee_per_byte is a Fibonacci number - if !is_fibonacci_number(self.core_fee_per_byte as u64) { + if !is_non_zero_fibonacci_number(self.core_fee_per_byte as u64) { return SimpleConsensusValidationResult::new_with_error( InvalidCreditWithdrawalTransitionCoreFeeError::new( self.core_fee_per_byte, @@ -212,6 +211,35 @@ impl StateTransitionStructureValidation for AddressCreditWithdrawalTransitionV0 ); } + // Validate that input_sum > output_amount (withdrawal amount must be positive) + let input_sum = input_sum.unwrap(); // Safe: checked above + let output_amount = self.output.as_ref().map_or(0, |(_, amount)| *amount); + if input_sum <= output_amount { + return SimpleConsensusValidationResult::new_with_error( + BasicError::WithdrawalBalanceMismatchError(WithdrawalBalanceMismatchError::new( + input_sum, + output_amount, + input_sum.saturating_sub(output_amount), + )) + .into(), + ); + } + + // Validate withdrawal amount meets minimum and maximum + let withdrawal_amount = input_sum - output_amount; // Safe: checked input_sum > output_amount above + if withdrawal_amount < MIN_WITHDRAWAL_AMOUNT + || withdrawal_amount > platform_version.system_limits.max_withdrawal_amount + { + return SimpleConsensusValidationResult::new_with_error( + BasicError::WithdrawalBelowMinAmountError(WithdrawalBelowMinAmountError::new( + withdrawal_amount, + MIN_WITHDRAWAL_AMOUNT, + platform_version.system_limits.max_withdrawal_amount, + )) + .into(), + ); + } + SimpleConsensusValidationResult::new() } } @@ -221,7 +249,9 @@ mod tests { use super::*; use crate::address_funds::AddressWitness; use crate::address_funds::PlatformAddress; + use crate::identity::core_script::CoreScript; use assert_matches::assert_matches; + use rand::SeedableRng; use std::collections::BTreeMap; #[test] @@ -243,6 +273,8 @@ mod tests { }, ], fee_strategy: vec![AddressFundsFeeStrategyStep::DeductFromInput(0)], + core_fee_per_byte: 1, // Valid Fibonacci number — ensures we reach the overflow check + output_script: CoreScript::random_p2pkh(&mut rand::rngs::StdRng::seed_from_u64(1)), ..Default::default() }; @@ -251,9 +283,7 @@ mod tests { assert_matches!( result.errors.as_slice(), [crate::consensus::ConsensusError::BasicError( - BasicError::InvalidCreditWithdrawalTransitionCoreFeeError( - InvalidCreditWithdrawalTransitionCoreFeeError { .. } - ) + BasicError::OverflowError(_) )] ); } diff --git a/packages/rs-dpp/src/state_transition/traits/state_transition_witness_validation.rs b/packages/rs-dpp/src/state_transition/traits/state_transition_witness_validation.rs index 481c74a843d..ec9d56ce610 100644 --- a/packages/rs-dpp/src/state_transition/traits/state_transition_witness_validation.rs +++ b/packages/rs-dpp/src/state_transition/traits/state_transition_witness_validation.rs @@ -50,15 +50,25 @@ pub trait StateTransitionWitnessValidation: StateTransitionWitnessSigned + Signa /// # Returns /// * `WitnessValidationResult` - Contains validation result and operations performed fn validate_witnesses(&self, signable_bytes: &[u8]) -> WitnessValidationResult { + let inputs = self.inputs(); + let witnesses = self.witnesses(); + + // Verify witness count matches input count before zipping + if inputs.len() != witnesses.len() { + return WitnessValidationResult::new_with_error( + InvalidStateTransitionSignatureError::new(format!( + "Number of witnesses ({}) does not match number of inputs ({})", + witnesses.len(), + inputs.len() + )) + .into(), + ); + } + let mut total_operations = AddressWitnessVerificationOperations::new(); // Validate each witness against its corresponding input address - for (i, (address, witness)) in self - .inputs() - .keys() - .zip(self.witnesses().iter()) - .enumerate() - { + for (i, (address, witness)) in inputs.keys().zip(witnesses.iter()).enumerate() { match address.verify_bytes_against_witness(witness, signable_bytes) { Ok(operations) => { total_operations.combine(&operations); diff --git a/packages/rs-dpp/src/util/is_fibonacci_number.rs b/packages/rs-dpp/src/util/is_fibonacci_number.rs deleted file mode 100644 index 4c736e0a9a0..00000000000 --- a/packages/rs-dpp/src/util/is_fibonacci_number.rs +++ /dev/null @@ -1,20 +0,0 @@ -fn is_perfect_square(number: u64) -> bool { - (number as f64).sqrt().fract() == 0.0 -} - -pub fn is_fibonacci_number(number: u64) -> bool { - let square_check_up = 5u64 - .checked_mul(number) - .and_then(|n| n.checked_mul(number)) - .and_then(|n| n.checked_add(4)); - - let square_check_down = 5u64 - .checked_mul(number) - .and_then(|n| n.checked_mul(number)) - .and_then(|n| n.checked_sub(4)); - - match (square_check_up, square_check_down) { - (Some(n1), Some(n2)) => is_perfect_square(n1) || is_perfect_square(n2), - _ => false, // Return false if either calculation overflows - } -} diff --git a/packages/rs-dpp/src/util/is_non_zero_fibonacci_number.rs b/packages/rs-dpp/src/util/is_non_zero_fibonacci_number.rs new file mode 100644 index 00000000000..8474a09f84d --- /dev/null +++ b/packages/rs-dpp/src/util/is_non_zero_fibonacci_number.rs @@ -0,0 +1,129 @@ +fn is_perfect_square(number: u64) -> bool { + if number < 2 { + return true; + } + // Integer square root via Newton's method + let mut x = number; + let mut y = x.div_ceil(2); + while y < x { + x = y; + y = (x + number / x) / 2; + } + x * x == number +} + +pub fn is_non_zero_fibonacci_number(number: u64) -> bool { + if number == 0 { + return false; + } + + let square_check_up = 5u64 + .checked_mul(number) + .and_then(|n| n.checked_mul(number)) + .and_then(|n| n.checked_add(4)); + + let square_check_down = 5u64 + .checked_mul(number) + .and_then(|n| n.checked_mul(number)) + .and_then(|n| n.checked_sub(4)); + + match (square_check_up, square_check_down) { + (Some(n1), Some(n2)) => is_perfect_square(n1) || is_perfect_square(n2), + (Some(n1), None) => is_perfect_square(n1), + (None, Some(n2)) => is_perfect_square(n2), + (None, None) => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// AUDIT M6: Floating-point imprecision in is_perfect_square for large values. + /// + /// `is_perfect_square` casts u64 to f64 and checks `sqrt().fract() == 0.0`. + /// f64 has only 52 bits of mantissa, so for values above 2^52, precision is lost. + /// This means `is_perfect_square` can return incorrect results for large numbers. + /// + /// Currently not exploitable for `core_fee_per_byte` (u32), but the function + /// accepts u64 and is technically unsound for large inputs. + /// + /// Location: rs-dpp/src/util/is_non_zero_fibonacci_number.rs:1-3 + #[test] + fn test_is_perfect_square_large_values() { + // For small values, is_perfect_square works correctly + assert!(is_perfect_square(0)); + assert!(is_perfect_square(1)); + assert!(is_perfect_square(4)); + assert!(is_perfect_square(9)); + assert!(is_perfect_square(16)); + assert!(!is_perfect_square(2)); + assert!(!is_perfect_square(3)); + + // Find a value where f64 imprecision causes is_perfect_square to give wrong answer. + // The number (2^26 + 1)^2 = 2^52 + 2^27 + 1 = 4503599761588225 + // When cast to f64, this may lose the +1 and appear as (2^26 + 1)^2 exactly, + // or nearby non-squares may appear as perfect squares. + // + // Strategy: find a non-square near a large perfect square where f64 rounds + // the sqrt to an exact integer. + let base: u64 = (1u64 << 26) + 1; // 67108865 + let perfect_square = base * base; // 4503599761588225 + + // Verify perfect_square is correctly identified + assert!( + is_perfect_square(perfect_square), + "AUDIT M6: {} should be a perfect square ({}^2)", + perfect_square, + base + ); + + // Check non-squares adjacent to large perfect squares. + // Due to f64 imprecision, some of these may incorrectly return true. + let false_positives: Vec = (1..=10u64) + .filter(|&offset| { + let candidate = perfect_square + offset; + // This should NOT be a perfect square, but f64 may say it is + is_perfect_square(candidate) + }) + .collect(); + + // If is_perfect_square is sound, there should be no false positives. + // With f64 imprecision, some non-squares will be falsely identified as perfect squares. + assert!( + false_positives.is_empty(), + "AUDIT M6: is_perfect_square returned true for non-square values near {}^2: \ + offsets {:?}. This is caused by f64 imprecision — (number as f64).sqrt() \ + rounds to an exact integer for these non-square values. \ + Fix: use integer square root instead of floating-point.", + base, + false_positives + ); + } + + /// AUDIT M6 (supplementary): Verify is_non_zero_fibonacci_number works for known values + #[test] + fn test_known_non_zero_fibonacci_numbers() { + // Known non-zero Fibonacci numbers + let fibs = [ + 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, + ]; + for &n in &fibs { + assert!( + is_non_zero_fibonacci_number(n), + "{} should be recognized as a Fibonacci number", + n + ); + } + + // Known non-Fibonacci numbers + let non_fibs = [0, 4, 6, 7, 9, 10, 11, 12, 14, 15, 16, 22, 100]; + for &n in &non_fibs { + assert!( + !is_non_zero_fibonacci_number(n), + "{} should NOT be recognized as a Fibonacci number", + n + ); + } + } +} diff --git a/packages/rs-dpp/src/util/mod.rs b/packages/rs-dpp/src/util/mod.rs index 2dee13be293..a95aa51923f 100644 --- a/packages/rs-dpp/src/util/mod.rs +++ b/packages/rs-dpp/src/util/mod.rs @@ -6,7 +6,7 @@ pub mod cbor_value; pub mod deserializer; pub mod entropy_generator; pub mod hash; -pub mod is_fibonacci_number; +pub mod is_non_zero_fibonacci_number; pub mod json_path; pub mod json_schema; pub mod json_value; diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_credit_withdrawal/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_credit_withdrawal/tests.rs index 68706b62a87..4fb2b771b80 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_credit_withdrawal/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_credit_withdrawal/tests.rs @@ -3921,7 +3921,7 @@ mod tests { #[test] fn test_large_amount_withdrawal() { - // Test withdrawal with very large amounts + // Test withdrawal at the maximum allowed amount (500 Dash) let platform_version = PlatformVersion::latest(); let platform_config = PlatformConfig { testing_configs: PlatformTestConfig { @@ -3939,18 +3939,16 @@ mod tests { let mut signer = TestAddressSigner::new(); let input_address = signer.add_p2pkh([1u8; 32]); - // Very large balance (1 billion credits) - let large_balance = 1_000_000_000_000_000_000u64; + // Balance large enough to cover max withdrawal + fees + let large_balance = dash_to_credits!(600.0); setup_address_with_balance(&mut platform, input_address, 0, large_balance); let mut rng = StdRng::seed_from_u64(567); let mut inputs = BTreeMap::new(); - // Withdraw most of it - inputs.insert( - input_address, - (1 as AddressNonce, large_balance - dash_to_credits!(1.0)), - ); + // Withdraw the max allowed amount (500 Dash = 50_000_000_000_000 credits) + let max_withdrawal = platform_version.system_limits.max_withdrawal_amount; + inputs.insert(input_address, (1 as AddressNonce, max_withdrawal)); let transition = create_signed_address_credit_withdrawal_transition( &signer, @@ -5830,4 +5828,444 @@ mod tests { ); } } + + mod security { + use super::*; + use dpp::state_transition::StateTransitionStructureValidation; + + /// AUDIT C1 (Structure): Output amount exceeds input sum — should be rejected. + /// + /// The withdrawal amount is computed as `total_inputs - output_amount`. + /// If `output_amount > total_inputs`, this subtraction underflows (panic in + /// debug, wrap in release). Structure validation currently does NOT check + /// that `output_amount <= input_sum`. + /// + /// Location: rs-drive/.../address_credit_withdrawal/v0/transformer.rs:40 + #[test] + fn test_output_exceeds_input_rejected_by_structure() { + let platform_version = PlatformVersion::latest(); + let mut rng = StdRng::seed_from_u64(999); + + let mut inputs = BTreeMap::new(); + inputs.insert( + create_platform_address(1), + (1 as AddressNonce, dash_to_credits!(0.01)), + ); + + // Output (change) is 100x larger than input sum + let output = Some((create_platform_address(2), dash_to_credits!(1.0))); + + let transition = AddressCreditWithdrawalTransitionV0 { + inputs, + output, + fee_strategy: AddressFundsFeeStrategy::from(vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + ]), + core_fee_per_byte: 1, + pooling: Pooling::Never, + output_script: create_random_output_script(&mut rng), + user_fee_increase: 0, + input_witnesses: vec![create_dummy_witness()], + }; + + let result = transition.validate_structure(platform_version); + + // SHOULD be invalid: output (1.0 Dash) > input (0.01 Dash) would + // cause underflow in withdrawal amount calculation. + // Currently FAILS: structure validation does not check this invariant. + assert!( + !result.is_valid(), + "AUDIT C1: Structure validation should reject when output amount ({}) exceeds \ + input sum ({}). The withdrawal amount would underflow to a huge u64 value.", + dash_to_credits!(1.0), + dash_to_credits!(0.01), + ); + } + + /// AUDIT C1 (Processing): When output > input reaches the transformer, + /// the unchecked subtraction panics in debug mode or wraps in release mode. + /// + /// This test submits a signed transition where output > input. Since + /// structure validation does not catch this, processing reaches the + /// transformer where `total_inputs - output_amount` underflows. + /// + /// Expected: the transition is rejected with an error. + /// Actual: panics (debug) or produces a wrapped withdrawal amount (release). + #[test] + fn test_output_exceeds_input_rejected_by_processing() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + // Address has 1.0 Dash balance + setup_address_with_balance(&mut platform, input_address, 0, dash_to_credits!(1.0)); + + let mut rng = StdRng::seed_from_u64(567); + + // Input takes 0.01 Dash from the address + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, dash_to_credits!(0.01))); + + // Output (change) requests 0.5 Dash — more than input sum of 0.01 Dash + // withdrawal_amount = 0.01 - 0.5 = UNDERFLOW + let output_address = create_platform_address(2); + + let transition = create_signed_address_credit_withdrawal_transition( + &signer, + inputs, + Some((output_address, dash_to_credits!(0.5))), + vec![AddressFundsFeeStrategyStep::DeductFromInput(0)], + create_random_output_script(&mut rng), + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + // In debug mode, this panics at `total_inputs - output_amount`. + // In release mode, the withdrawal amount wraps to u64::MAX - delta. + // Either way, the transition should be REJECTED, not succeed. + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition without panic"); + + // Should NOT be a successful execution — output > input must be rejected + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::WithdrawalBalanceMismatchError(_)) + )] + ); + } + + /// AUDIT M1: Fee deduction BTreeMap index shifting after entry removal. + /// + /// When fee strategy step DeductFromInput(0) drains input A to zero, + /// A is removed from the BTreeMap. The next step DeductFromInput(1) + /// now targets what was originally at index 2 (C) instead of index 1 (B), + /// because all indices shifted down after the removal. + /// + /// Location: rs-dpp/.../deduct_fee_from_inputs_and_outputs/v0/mod.rs:35-45 + #[test] + fn test_fee_deduction_stable_after_entry_removal() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let addr_a = signer.add_p2pkh([10u8; 32]); + let addr_b = signer.add_p2pkh([20u8; 32]); + let addr_c = signer.add_p2pkh([30u8; 32]); + + // Determine BTreeMap sort order + let mut sorted_addrs = vec![addr_a, addr_b, addr_c]; + sorted_addrs.sort(); + let first = sorted_addrs[0]; + let second = sorted_addrs[1]; + let third = sorted_addrs[2]; + + let first_balance = dash_to_credits!(0.1); + let second_balance = dash_to_credits!(1.0); + let third_balance = dash_to_credits!(1.0); + + // Input amount leaves only 1000 credits remaining for first + let first_input = first_balance - 1000; + let second_input = dash_to_credits!(0.01); + let third_input = dash_to_credits!(0.01); + + setup_address_with_balance(&mut platform, first, 0, first_balance); + setup_address_with_balance(&mut platform, second, 0, second_balance); + setup_address_with_balance(&mut platform, third, 0, third_balance); + + let mut rng = StdRng::seed_from_u64(567); + + let mut inputs = BTreeMap::new(); + inputs.insert(first, (1 as AddressNonce, first_input)); + inputs.insert(second, (1 as AddressNonce, second_input)); + inputs.insert(third, (1 as AddressNonce, third_input)); + + // Fee strategy: deduct from index 0 (first), then index 1 (should be second). + let transition = create_signed_address_credit_withdrawal_transition( + &signer, + inputs, + None, + vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + AddressFundsFeeStrategyStep::DeductFromInput(1), + ], + create_random_output_script(&mut rng), + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction should succeed" + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + let second_remaining_before_fee = second_balance - second_input; + + let (_, second_final) = platform + .drive + .fetch_balance_and_nonce(&second, None, platform_version) + .expect("should fetch") + .expect("second address should exist"); + + assert!( + second_final < second_remaining_before_fee, + "AUDIT M1: Fee should have been deducted from second address (original \ + BTreeMap index 1), but it was deducted from third address instead. \ + After first was drained (1000 credits) and removed from BTreeMap, \ + DeductFromInput(1) shifted to target the third address. \ + second's balance: {} (expected < {})", + second_final, + second_remaining_before_fee + ); + } + + /// AUDIT H2: Missing MIN_WITHDRAWAL_AMOUNT validation for address withdrawals. + /// + /// The identity credit withdrawal transition checks MIN_WITHDRAWAL_AMOUNT at + /// `identity_credit_withdrawal/structure/v1/mod.rs:33`, but the address credit + /// withdrawal transition has no such check. An attacker can create a withdrawal + /// with a tiny amount (e.g., 1 credit) which is economically irrational and + /// wastes chain resources (creates a Core transaction for dust). + /// + /// This test creates a withdrawal where `withdrawal_amount = input_sum - output_amount` + /// is very small (1000 credits = dust) and verifies that structure validation rejects it. + /// + /// Location: rs-dpp/.../address_credit_withdrawal/v0/state_transition_validation.rs + #[test] + fn test_withdrawal_below_min_amount_rejected_by_structure() { + let platform_version = PlatformVersion::latest(); + let mut rng = StdRng::seed_from_u64(999); + + // Input has 0.1 Dash, output takes back 0.1 Dash minus 1000 credits + // This leaves withdrawal_amount = 1000 credits (dust) + let input_amount = dash_to_credits!(0.1); + let output_amount = input_amount - 1000; // Leave only 1000 credits for withdrawal + + let mut inputs = BTreeMap::new(); + inputs.insert( + create_platform_address(1), + (1 as AddressNonce, input_amount), + ); + + let output = Some((create_platform_address(2), output_amount)); + + let transition = AddressCreditWithdrawalTransitionV0 { + inputs, + output, + fee_strategy: AddressFundsFeeStrategy::from(vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + ]), + core_fee_per_byte: 1, + pooling: Pooling::Never, + output_script: create_random_output_script(&mut rng), + user_fee_increase: 0, + input_witnesses: vec![create_dummy_witness()], + }; + + let result = transition.validate_structure(platform_version); + + // SHOULD be invalid: 1000 credits is below any reasonable MIN_WITHDRAWAL_AMOUNT. + // The identity credit withdrawal enforces this, but address withdrawal does not. + assert!( + !result.is_valid(), + "AUDIT H2: Dust withdrawal of 1000 credits (input {} - output {} = {}) \ + should be rejected by structure validation. Identity credit withdrawal \ + enforces MIN_WITHDRAWAL_AMOUNT but address credit withdrawal does not. \ + This allows economically irrational withdrawals that waste Core chain \ + resources creating dust transactions.", + input_amount, + output_amount, + input_amount - output_amount, + ); + } + + /// AUDIT H2 (Processing): Dust withdrawal reaches processing without rejection. + /// + /// Signed version of the H2 test that goes through the full processing pipeline. + #[test] + fn test_withdrawal_below_min_amount_rejected_by_processing() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + setup_address_with_balance(&mut platform, input_address, 0, dash_to_credits!(1.0)); + + let mut rng = StdRng::seed_from_u64(567); + + // Input takes 0.1 Dash, output returns all but 1000 credits + let input_amount = dash_to_credits!(0.1); + let output_amount = input_amount - 1000; + + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, input_amount)); + + let output_address = create_platform_address(2); + + let transition = create_signed_address_credit_withdrawal_transition( + &signer, + inputs, + Some((output_address, output_amount)), + vec![AddressFundsFeeStrategyStep::DeductFromInput(0)], + create_random_output_script(&mut rng), + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // Dust withdrawal should NOT succeed + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::WithdrawalBelowMinAmountError(_)) + )] + ); + } + + /// AUDIT H3: Unchecked `.sum()` in withdrawal transformer uses wrapping arithmetic. + /// + /// At `transformer.rs:36`, the withdrawal transformer computes input sum using + /// `.sum()` which wraps on overflow in release mode. Structure validation has + /// an upstream overflow check, but if bypassed, the transformer would produce + /// a wrapped (incorrect) sum. This is a defense-in-depth violation. + /// + /// This test verifies that structure validation correctly catches the overflow + /// (complementing M7). The transformer code should also use `checked_add`. + /// + /// Location: rs-drive/.../address_credit_withdrawal/v0/transformer.rs:36 + #[test] + fn test_transformer_input_sum_uses_checked_add() { + let platform_version = PlatformVersion::latest(); + let mut rng = StdRng::seed_from_u64(999); + + // Two inputs that sum to > u64::MAX + let mut inputs = BTreeMap::new(); + inputs.insert(create_platform_address(1), (0 as AddressNonce, u64::MAX)); + inputs.insert(create_platform_address(2), (0 as AddressNonce, u64::MAX)); + + let transition = AddressCreditWithdrawalTransitionV0 { + inputs, + output: None, + fee_strategy: AddressFundsFeeStrategy::from(vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + ]), + core_fee_per_byte: 1, // Valid Fibonacci number + pooling: Pooling::Never, + output_script: create_random_output_script(&mut rng), + user_fee_increase: 0, + input_witnesses: vec![create_dummy_witness(), create_dummy_witness()], + }; + + let result = transition.validate_structure(platform_version); + + // Structure validation should catch the overflow + assert!( + !result.is_valid(), + "AUDIT H3: Two inputs of u64::MAX should cause overflow. Structure validation \ + catches this, but the transformer at transformer.rs:36 uses .sum() which would \ + silently wrap in release mode if structure validation were bypassed. \ + The transformer should use checked_add for defense-in-depth." + ); + + // Verify it's specifically an overflow error (not some other error) + let has_overflow_error = result + .errors + .iter() + .any(|e| matches!(e, ConsensusError::BasicError(BasicError::OverflowError(_)))); + assert!( + has_overflow_error, + "AUDIT H3: Expected OverflowError for input sum overflow, got {:?}", + result.errors + ); + } + } } 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 0d524484384..090e49840ea 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 @@ -8920,4 +8920,671 @@ mod tests { } } } + + mod security { + use super::*; + + /// AUDIT M1: Fee deduction BTreeMap index shifting after entry removal. + /// + /// When fee strategy step DeductFromInput(0) drains input A to zero, + /// A is removed from the BTreeMap. The next step DeductFromInput(1) + /// now targets what was originally at index 2 (C) instead of index 1 (B), + /// because all indices shifted down after the removal. + /// + /// Location: rs-dpp/.../deduct_fee_from_inputs_and_outputs/v0/mod.rs:35-45 + #[test] + fn test_fee_deduction_stable_after_entry_removal() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let addr_a = signer.add_p2pkh([10u8; 32]); + let addr_b = signer.add_p2pkh([20u8; 32]); + let addr_c = signer.add_p2pkh([30u8; 32]); + + // Determine BTreeMap sort order + let mut sorted_addrs = vec![addr_a, addr_b, addr_c]; + sorted_addrs.sort(); + let first = sorted_addrs[0]; + let second = sorted_addrs[1]; + let third = sorted_addrs[2]; + + let first_balance = dash_to_credits!(0.1); + let second_balance = dash_to_credits!(1.0); + let third_balance = dash_to_credits!(1.0); + + // Input amount leaves only 1000 credits remaining for first + let first_input = first_balance - 1000; + let second_input = dash_to_credits!(0.01); + let third_input = dash_to_credits!(0.01); + + setup_address_with_balance(&mut platform, first, 0, first_balance); + setup_address_with_balance(&mut platform, second, 0, second_balance); + setup_address_with_balance(&mut platform, third, 0, third_balance); + + let mut rng = StdRng::seed_from_u64(567); + let (asset_lock_proof, asset_lock_pk) = create_asset_lock_proof_with_key(&mut rng); + // Asset lock provides ~1.0 Dash of credits + + let mut inputs = BTreeMap::new(); + inputs.insert(first, (1 as AddressNonce, first_input)); + inputs.insert(second, (1 as AddressNonce, second_input)); + inputs.insert(third, (1 as AddressNonce, third_input)); + + let remainder_address = create_platform_address(100); + let mut outputs = BTreeMap::new(); + outputs.insert(remainder_address, None); // remainder output + + // Fee strategy: deduct from index 0 (first), then index 1 (should be second). + let transition = create_signed_address_funding_from_asset_lock_transition( + asset_lock_proof, + &asset_lock_pk, + &signer, + inputs, + outputs, + vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + AddressFundsFeeStrategyStep::DeductFromInput(1), + ], + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction should succeed" + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + let second_remaining_before_fee = second_balance - second_input; + + let (_, second_final) = platform + .drive + .fetch_balance_and_nonce(&second, None, platform_version) + .expect("should fetch") + .expect("second address should exist"); + + assert!( + second_final < second_remaining_before_fee, + "AUDIT M1: Fee should have been deducted from second address (original \ + BTreeMap index 1), but it was deducted from third address instead. \ + After first was drained (1000 credits) and removed from BTreeMap, \ + DeductFromInput(1) shifted to target the third address. \ + second's balance: {} (expected < {})", + second_final, + second_remaining_before_fee + ); + } + + /// AUDIT M2: ReduceOutput index invalidated after remainder removal. + /// + /// When `total_available == explicit_outputs_sum`, the remainder output is + /// removed at `advanced_structure/v0/mod.rs:84-86`. If the fee strategy + /// includes `ReduceOutput(i)` targeting the remainder position, the index + /// becomes out-of-bounds after removal. Fee deduction silently skips the + /// step, meaning the attacker gets a free (zero-fee) transaction. + /// + /// This test creates a transition where total available equals explicit outputs + /// (so remainder is 0 and removed), with a fee strategy targeting the removed output. + /// + /// Location: rs-dpp/.../address_funding_from_asset_lock/advanced_structure/v0/mod.rs:84-86 + #[test] + fn test_reduce_output_index_after_remainder_removal() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let signer = TestAddressSigner::new(); + let mut rng = StdRng::seed_from_u64(567); + let (asset_lock_proof, asset_lock_pk) = create_asset_lock_proof_with_key(&mut rng); + // Asset lock provides ~1.0 Dash of credits (100_000_000 duffs * 1000) + let asset_lock_credits = dash_to_credits!(1.0); + + // Create two explicit outputs that sum to exactly the asset lock amount. + // This means remainder = 0, which triggers remainder removal. + let output_addr_1 = create_platform_address(1); + let output_addr_2 = create_platform_address(2); + + let mut outputs = BTreeMap::new(); + outputs.insert(output_addr_1, Some(asset_lock_credits / 2)); + outputs.insert(output_addr_2, Some(asset_lock_credits / 2)); + + // Fee strategy targets ReduceOutput(2) — originally the remainder position. + // After remainder is removed (outputs shrink from 3 to 2), index 2 is OOB. + // Fee deduction may silently skip, giving a free transaction. + let transition = create_signed_address_funding_from_asset_lock_transition( + asset_lock_proof, + &asset_lock_pk, + &signer, + BTreeMap::new(), // No address inputs + outputs, + vec![AddressFundsFeeStrategyStep::ReduceOutput(2)], + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // The transition should either: + // 1. Be rejected because the fee strategy references an invalid index, OR + // 2. Have fees properly deducted from a different output. + // It should NOT succeed with zero fees paid. + match processing_result.execution_results().as_slice() { + [StateTransitionExecutionResult::SuccessfulExecution { .. }] => { + // If it succeeded, verify fees were actually deducted + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + let bal_1 = platform + .drive + .fetch_balance_and_nonce(&output_addr_1, None, platform_version) + .expect("should fetch") + .map(|(_, b)| b) + .unwrap_or(0); + + let bal_2 = platform + .drive + .fetch_balance_and_nonce(&output_addr_2, None, platform_version) + .expect("should fetch") + .map(|(_, b)| b) + .unwrap_or(0); + + let total_output = bal_1 + bal_2; + + // If fees were properly deducted, total_output < asset_lock_credits + assert!( + total_output < asset_lock_credits, + "AUDIT M2: Transaction succeeded but total output ({}) equals \ + asset lock credits ({}). Fees were not deducted because \ + ReduceOutput(2) targeted the removed remainder position. \ + The fee deduction was silently skipped, giving a free transaction.", + total_output, + asset_lock_credits, + ); + } + _ => { + // Rejected — this is acceptable behavior (fee strategy validation caught it) + } + } + } + + /// AUDIT M5: No explicit conservation-of-credits check (asset lock only). + /// + /// This test verifies credit conservation when using only an asset lock + /// (no address inputs). All asset lock credits should end up in outputs + fees. + /// This is a standalone conservation test complementing C2. + /// + /// Location: rs-drive/.../address_funding_from_asset_lock_transition.rs + #[test] + fn test_credits_conservation_with_asset_lock_only() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let signer = TestAddressSigner::new(); + let mut rng = StdRng::seed_from_u64(567); + let (asset_lock_proof, asset_lock_pk) = create_asset_lock_proof_with_key(&mut rng); + let asset_lock_credits = dash_to_credits!(1.0); + + // Pure asset lock funding — no address inputs + let output_addr = create_platform_address(1); + let remainder_addr = create_platform_address(2); + + let explicit_amount = dash_to_credits!(0.3); + let mut outputs = BTreeMap::new(); + outputs.insert(output_addr, Some(explicit_amount)); + outputs.insert(remainder_addr, None); // remainder + + let transition = create_signed_address_funding_from_asset_lock_transition( + asset_lock_proof, + &asset_lock_pk, + &signer, + BTreeMap::new(), // No address inputs + outputs, + vec![AddressFundsFeeStrategyStep::ReduceOutput(0)], + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Pure asset lock funding should succeed" + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + // Read final balances + let output_final = platform + .drive + .fetch_balance_and_nonce(&output_addr, None, platform_version) + .expect("should fetch") + .map(|(_, b)| b) + .unwrap_or(0); + + let remainder_final = platform + .drive + .fetch_balance_and_nonce(&remainder_addr, None, platform_version) + .expect("should fetch") + .map(|(_, b)| b) + .unwrap_or(0); + + let total_in_addresses = output_final + remainder_final; + + // Conservation: all asset lock credits should be in outputs + fees + // total_in_addresses + fees_paid = asset_lock_credits + // So: total_in_addresses <= asset_lock_credits + // total_in_addresses >= asset_lock_credits - max_reasonable_fee + let max_reasonable_fee = dash_to_credits!(0.01); + + assert!( + total_in_addresses >= asset_lock_credits - max_reasonable_fee, + "AUDIT M5: Credits not conserved. Asset lock provided {} credits, \ + but output addresses only received {} credits total. \ + Expected at least {} (asset_lock - max_fee {}). \ + There is no explicit conservation-of-credits assertion in the processing code.", + asset_lock_credits, + total_in_addresses, + asset_lock_credits - max_reasonable_fee, + max_reasonable_fee, + ); + + assert!( + total_in_addresses <= asset_lock_credits, + "AUDIT M5: Output total {} exceeds asset lock credits {}. \ + Credits were created from nothing!", + total_in_addresses, + asset_lock_credits, + ); + } + + /// AUDIT M3: Remainder arithmetic uses unchecked operations. + /// + /// At `address_funding_from_asset_lock/mod.rs:61,64`, the transformer uses + /// `.sum()` and unchecked subtraction for remainder computation. If structure + /// validation is bypassed, these operations could wrap/underflow. + /// + /// This test verifies that structure validation catches the overflow at the + /// correct level, but notes the transformer lacks defense-in-depth. + /// + /// Location: rs-drive/.../address_funding_from_asset_lock/mod.rs:61,64 + #[test] + fn test_remainder_arithmetic_uses_checked_operations() { + use crate::execution::validation::state_transition::processor::traits::basic_structure::StateTransitionBasicStructureValidationV0; + + let platform_version = PlatformVersion::latest(); + + let mut rng = StdRng::seed_from_u64(567); + let (asset_lock_proof, _asset_lock_pk) = create_asset_lock_proof_with_key(&mut rng); + + // Create outputs that sum to > u64::MAX, with a remainder output + let output_addr_1 = create_platform_address(1); + let output_addr_2 = create_platform_address(2); + let remainder_addr = create_platform_address(3); + + let mut outputs = BTreeMap::new(); + outputs.insert(output_addr_1, Some(u64::MAX)); + outputs.insert(output_addr_2, Some(u64::MAX)); + outputs.insert(remainder_addr, None); // Remainder output + + let transition = create_raw_transition_with_dummy_witnesses( + asset_lock_proof, + BTreeMap::new(), + outputs, + AddressFundsFeeStrategy::from(vec![AddressFundsFeeStrategyStep::ReduceOutput(0)]), + 0, + ); + + let result = transition + .validate_basic_structure(dpp::dashcore::Network::Testnet, platform_version) + .expect("validation should not return Err"); + + assert!(!result.is_valid()); + assert_matches!( + result.first_error().unwrap(), + ConsensusError::BasicError(BasicError::OverflowError(_)) + ); + } + + /// AUDIT C2: Remainder calculation ignores input contributions — funds destroyed. + /// + /// When a transition combines an asset lock with existing address inputs, + /// the remainder is computed as `asset_lock_balance - explicit_outputs_sum`, + /// completely ignoring the input contributions. The input funds are deducted + /// from the source address but never routed to any output — credits are + /// permanently destroyed. + /// + /// Locations: + /// - rs-drive/.../address_funding_from_asset_lock/mod.rs:64 (resolved_outputs) + /// - rs-drive/.../address_funding_from_asset_lock_transition.rs:61 (operations) + #[test] + fn test_remainder_includes_input_contributions() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + let input_balance = dash_to_credits!(0.5); + let input_amount = dash_to_credits!(0.3); + setup_address_with_balance(&mut platform, input_address, 0, input_balance); + + let mut rng = StdRng::seed_from_u64(567); + let (asset_lock_proof, asset_lock_pk) = create_asset_lock_proof_with_key(&mut rng); + // Asset lock provides ~1.0 Dash of credits + + // Combine asset lock + existing address input, all going to remainder + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, input_amount)); + + let remainder_address = create_platform_address(2); + let mut outputs = BTreeMap::new(); + outputs.insert(remainder_address, None); // Single remainder output + + let transition = create_signed_address_funding_from_asset_lock_transition( + asset_lock_proof, + &asset_lock_pk, + &signer, + inputs, + outputs, + vec![AddressFundsFeeStrategyStep::ReduceOutput(0)], + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction should succeed" + ); + + // Commit so we can read final balances + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + // Check remainder balance + let remainder_result = platform + .drive + .fetch_balance_and_nonce(&remainder_address, None, platform_version) + .expect("should fetch"); + + let remainder_balance = remainder_result.map(|(_, balance)| balance).unwrap_or(0); + + // Expected: asset_lock (~1.0 Dash) + input (0.3 Dash) - fees + // = ~1.3 Dash - small_fee > 1.2 Dash + // Actual (BUG): asset_lock (~1.0 Dash) - fees < 1.0 Dash + // The 0.3 Dash from the input address is deducted but never added + // to any output — those credits are permanently destroyed. + assert!( + remainder_balance > dash_to_credits!(1.2), + "AUDIT C2: Remainder should include input contributions. \ + Expected > {} credits (~1.3 Dash - fees), got {} credits. \ + The input contribution of {} credits ({} Dash) was deducted from \ + the source address but never routed to any output — destroyed.", + dash_to_credits!(1.2), + remainder_balance, + input_amount, + "0.3" + ); + } + + /// AUDIT C2 (conservation): Verify total credits are conserved. + /// + /// After processing an asset-lock-with-inputs transition, the total + /// credits across all affected addresses plus the withdrawal document + /// should equal the initial credits. Currently, input contributions + /// vanish, violating conservation. + #[test] + fn test_credits_conservation_with_inputs() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + let input_balance = dash_to_credits!(1.0); + let input_amount = dash_to_credits!(0.5); + setup_address_with_balance(&mut platform, input_address, 0, input_balance); + + let mut rng = StdRng::seed_from_u64(567); + let (asset_lock_proof, asset_lock_pk) = create_asset_lock_proof_with_key(&mut rng); + let asset_lock_credits = dash_to_credits!(1.0); // ~1 Dash from fixture + + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, input_amount)); + + let explicit_address = create_platform_address(2); + let remainder_address = create_platform_address(3); + let explicit_amount = dash_to_credits!(0.2); + + let mut outputs = BTreeMap::new(); + outputs.insert(explicit_address, Some(explicit_amount)); + outputs.insert(remainder_address, None); + + let transition = create_signed_address_funding_from_asset_lock_transition( + asset_lock_proof, + &asset_lock_pk, + &signer, + inputs, + outputs, + vec![AddressFundsFeeStrategyStep::ReduceOutput(0)], + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction should succeed" + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + // Read all final balances + let _input_final = platform + .drive + .fetch_balance_and_nonce(&input_address, None, platform_version) + .expect("should fetch") + .map(|(_, b)| b) + .unwrap_or(0); + + let explicit_final = platform + .drive + .fetch_balance_and_nonce(&explicit_address, None, platform_version) + .expect("should fetch") + .map(|(_, b)| b) + .unwrap_or(0); + + let remainder_final = platform + .drive + .fetch_balance_and_nonce(&remainder_address, None, platform_version) + .expect("should fetch") + .map(|(_, b)| b) + .unwrap_or(0); + + // Total credits in = asset_lock + input_amount + let total_credits_in = asset_lock_credits + input_amount; + + // Total credits out = input_remaining + explicit + remainder + fees + // input_remaining = input_balance - input_amount + let _input_remaining = input_balance - input_amount; + let total_credits_in_addresses = explicit_final + remainder_final; + + // The output addresses should have received total_credits_in minus fees. + // input_final should equal input_remaining minus any fee deducted from it. + // Conservation: explicit_final + remainder_final >= total_credits_in - max_reasonable_fee + let max_reasonable_fee = dash_to_credits!(0.01); // Fees should be small + + assert!( + total_credits_in_addresses >= total_credits_in - max_reasonable_fee, + "AUDIT C2: Credits not conserved. Input contributed {} credits but \ + output addresses only received {} credits total. Expected at least {} \ + (total_in {} - max_fee {}). The input contribution was destroyed.", + input_amount, + total_credits_in_addresses, + total_credits_in - max_reasonable_fee, + total_credits_in, + max_reasonable_fee, + ); + } + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs index ac3a14621aa..034a7992d53 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/address_funds_transfer/tests.rs @@ -14,6 +14,7 @@ mod tests { }; use dpp::block::block_info::BlockInfo; use dpp::consensus::basic::BasicError; + use dpp::consensus::signature::SignatureError; use dpp::consensus::state::state_error::StateError; use dpp::consensus::ConsensusError; use dpp::dash_to_credits; @@ -382,7 +383,9 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), [StateTransitionExecutionResult::UnpaidConsensusError( - ConsensusError::BasicError(BasicError::InputWitnessCountMismatchError(_)) + ConsensusError::SignatureError( + SignatureError::InvalidStateTransitionSignatureError(_) + ) )] ); } @@ -5237,10 +5240,12 @@ mod tests { // Should fail with some error (not panic) assert!(!processing_result.execution_results().is_empty()); - assert!(!matches!( - processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - )); + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] + ); } } @@ -6100,12 +6105,11 @@ mod tests { .expect("expected to process"); // Should fail - timelock scripts should not be accepted - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "Timelock (CLTV) script should not be accepted" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -6176,12 +6180,11 @@ mod tests { .expect("expected to process"); // Should fail - either invalid script format or signature verification fails - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "OP_RETURN script should not be accepted" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -6565,12 +6568,11 @@ mod tests { .expect("expected to process"); // MUST fail - OP_TRUE script without proper multisig structure is not valid - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "OP_TRUE script should NOT be accepted - this would be a critical vulnerability!" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -6639,12 +6641,11 @@ mod tests { ) .expect("expected to process"); - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "OP_1 script should NOT be accepted" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -6831,12 +6832,11 @@ mod tests { ) .expect("expected to process"); - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "Disabled opcode OP_CAT should not be accepted" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -6905,12 +6905,11 @@ mod tests { ) .expect("expected to process"); - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "Disabled opcode OP_VER should not be accepted" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -6986,12 +6985,11 @@ mod tests { .expect("expected to process"); // Large scripts should be rejected (or at least the OP_1 should fail validation) - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "Very large redeem script should be rejected" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -7187,12 +7185,11 @@ mod tests { .expect("expected to process"); // Non-canonical DER should be rejected - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "Non-canonical DER signature should be rejected" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -7262,12 +7259,11 @@ mod tests { .expect("expected to process"); // Empty script should fail (leaves empty stack) - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "Empty script should be rejected" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -7339,12 +7335,11 @@ mod tests { .expect("expected to process"); // OP_CODESEPARATOR in non-standard script should be rejected - assert!( - !matches!( - &processing_result.execution_results()[0], - StateTransitionExecutionResult::SuccessfulExecution { .. } - ), - "Script with OP_CODESEPARATOR should be rejected" + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::SerializedObjectParsingError(_)) + )] ); } @@ -8291,4 +8286,397 @@ mod tests { ); } } + + mod security_edge_cases { + use super::*; + use dpp::consensus::signature::SignatureError; + + /// AUDIT H1: Witness validation uses zip() without count check. + /// + /// `validate_witnesses` pairs inputs with witnesses using `zip()`, which + /// silently stops at the shorter iterator. With 0 witnesses on 2 inputs, + /// zip produces 0 iterations and validation "passes" (no errors). + /// The witness count mismatch is only caught later by structure validation. + /// + /// This test verifies that the PROCESSING pipeline rejects a transition + /// with fewer witnesses than inputs as an UNPAID error (meaning the + /// witness validation stage caught it, not the structure validation stage). + /// + /// Location: rs-dpp/.../state_transition_witness_validation.rs:56-60 + #[test] + fn test_zero_witnesses_rejected_by_witness_validation() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let addr_a = signer.add_p2pkh([1u8; 32]); + let addr_b = signer.add_p2pkh([2u8; 32]); + + setup_address_with_balance(&mut platform, addr_a, 0, dash_to_credits!(1.0)); + setup_address_with_balance(&mut platform, addr_b, 0, dash_to_credits!(1.0)); + + // Create a transition with 2 inputs but 0 witnesses. + // The validate_witnesses zip() produces 0 iterations → "passes". + // Only structure validation catches the mismatch later. + let mut inputs = BTreeMap::new(); + inputs.insert(addr_a, (1 as AddressNonce, dash_to_credits!(0.1))); + inputs.insert(addr_b, (1 as AddressNonce, dash_to_credits!(0.1))); + + let mut outputs = BTreeMap::new(); + outputs.insert(create_platform_address(10), dash_to_credits!(0.2)); + + // Construct transition with 0 witnesses (not matching 2 inputs) + let transition = create_raw_transition_with_dummy_witnesses( + inputs, + outputs, + AddressFundsFeeStrategy::from(vec![AddressFundsFeeStrategyStep::DeductFromInput( + 0, + )]), + 0, // 0 witnesses for 2 inputs + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // The transition is correctly rejected. But the question is: by WHICH + // validation stage? If witness validation caught it (correct), it would + // be an InvalidStateTransitionSignatureError (unpaid). If structure + // validation caught it (current buggy behavior), it would be + // InputWitnessCountMismatchError (also unpaid). + // + // We assert it should be a signature error (from witness validation), + // not a count mismatch error (from structure validation that runs later). + // Currently FAILS: witness validation passes with 0 iterations, + // and structure validation catches it as InputWitnessCountMismatchError. + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::SignatureError( + SignatureError::InvalidStateTransitionSignatureError(_) + ) + )], + "AUDIT H1: With 0 witnesses on 2 inputs, witness validation should catch \ + the mismatch and return InvalidStateTransitionSignatureError. Currently, \ + zip() produces 0 iterations so witness validation passes, and the error \ + is caught later by structure validation as InputWitnessCountMismatchError. \ + Got: {:?}", + processing_result.execution_results() + ); + } + + /// AUDIT M4: Credits (u64) cast to SumValue (i64) truncation destroys funds. + /// + /// When setting an address balance, `set_balance_to_address_operations_v0` + /// casts `balance as SumValue` where SumValue is i64. For balance values + /// > i64::MAX, this would produce a negative value. The Drive layer now + /// guards against this with an overflow check, returning an error instead + /// of silently truncating. + /// + /// In practice this can never happen because MAX_CREDITS == i64::MAX and + /// all input validation enforces this, but the Drive layer should still + /// reject such values defensively. + /// + /// Location: rs-drive/src/drive/address_funds/set_balance_to_address/v0/mod.rs + #[test] + fn test_balance_exceeding_i64_max_returns_overflow_error() { + let platform_version = PlatformVersion::latest(); + + let platform = TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let address = PlatformAddress::P2pkh([1u8; 20]); + let large_balance: u64 = i64::MAX as u64 + 1; // 9_223_372_036_854_775_808 + + // Attempting to set a balance > i64::MAX should return an overflow error + let mut drive_operations = Vec::new(); + let result = platform.drive.set_balance_to_address( + address, + 0, + large_balance, + &mut None, + &mut drive_operations, + platform_version, + ); + + assert!( + result.is_err(), + "AUDIT M4: set_balance_to_address should reject balance {} (> i64::MAX = {}) \ + with an overflow error to prevent u64→i64 truncation in sum tree storage.", + large_balance, + i64::MAX, + ); + + // Verify that i64::MAX itself is accepted (boundary value) + let mut drive_operations = Vec::new(); + let result = platform.drive.set_balance_to_address( + address, + 0, + i64::MAX as u64, + &mut None, + &mut drive_operations, + platform_version, + ); + + assert!( + result.is_ok(), + "set_balance_to_address should accept balance == i64::MAX" + ); + } + + /// AUDIT L2: Nonce overflow at u32::MAX locks address permanently. + /// + /// `AddressNonce` is defined as `u32` (rs-dpp/src/lib.rs:117), meaning an + /// address can be used at most ~4.3 billion times. After nonce reaches u32::MAX, + /// no further transactions are possible — the address is permanently locked. + /// + /// This test sets up an address with nonce at u32::MAX - 1, performs one + /// transaction (nonce becomes u32::MAX), then attempts another transaction + /// to verify the address cannot transact further. + /// + /// Location: rs-dpp/src/lib.rs:117 + #[test] + fn test_nonce_at_u32_max_locks_address() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let input_address = signer.add_p2pkh([1u8; 32]); + + // Set up address with nonce at u32::MAX - 1 and large balance + let nonce_near_max: AddressNonce = u32::MAX - 1; + setup_address_with_balance( + &mut platform, + input_address, + nonce_near_max, + dash_to_credits!(10.0), + ); + + let recipient = create_platform_address(2); + + // First transaction: nonce = u32::MAX (should succeed) + let transition1 = create_signed_address_funds_transfer_transition( + &signer, + input_address, + u32::MAX, // nonce = u32::MAX + dash_to_credits!(0.01), + recipient, + dash_to_credits!(0.01), + ); + + let result1 = transition1.serialize_to_bytes().expect("should serialize"); + let platform_state = platform.state.load(); + let transaction1 = platform.drive.grove.start_transaction(); + + let processing_result1 = platform + .platform + .process_raw_state_transitions( + &vec![result1], + &platform_state, + &BlockInfo::default(), + &transaction1, + platform_version, + false, + None, + ) + .expect("expected to process"); + + // This transaction should succeed (nonce u32::MAX is valid) + assert_matches!( + processing_result1.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction with nonce u32::MAX should succeed" + ); + + platform + .drive + .grove + .commit_transaction(transaction1) + .unwrap() + .expect("should commit"); + + // After this, the address nonce is at u32::MAX. + // Any further transaction would need nonce = u32::MAX + 1 = overflow. + // The address is effectively locked forever. + // This test documents the behavior — it's a low-severity issue since + // 4.3 billion transactions per address is practically unreachable, + // but there's no graceful handling or error message. + // + // Note: We can't easily test the "next" transaction because we'd need + // nonce u32::MAX + 1 which wraps to 0 and would be rejected as a + // nonce reuse. This is documented as a design limitation. + } + + /// AUDIT M1: Fee deduction BTreeMap index shifting after entry removal. + /// + /// When fee strategy step DeductFromInput(0) drains input A to zero, + /// A is removed from the BTreeMap. The next step DeductFromInput(1) + /// now targets what was originally at index 2 (C) instead of index 1 (B), + /// because all indices shifted down after the removal. + /// + /// Location: rs-dpp/.../deduct_fee_from_inputs_and_outputs/v0/mod.rs:35-45 + #[test] + fn test_fee_deduction_stable_after_entry_removal() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut signer = TestAddressSigner::new(); + let addr_a = signer.add_p2pkh([10u8; 32]); + let addr_b = signer.add_p2pkh([20u8; 32]); + let addr_c = signer.add_p2pkh([30u8; 32]); + + // Determine BTreeMap sort order (PlatformAddress sorts by hash) + let mut sorted_addrs = vec![addr_a, addr_b, addr_c]; + sorted_addrs.sort(); + let first = sorted_addrs[0]; // BTreeMap index 0 + let second = sorted_addrs[1]; // BTreeMap index 1 + let third = sorted_addrs[2]; // BTreeMap index 2 + + // Set up balances so that "first" has tiny remaining balance after transfer + let first_balance = dash_to_credits!(0.1); + let second_balance = dash_to_credits!(1.0); + let third_balance = dash_to_credits!(1.0); + + // Input amount leaves only 1000 credits remaining for first + let first_input = first_balance - 1000; + let second_input = dash_to_credits!(0.01); + let third_input = dash_to_credits!(0.01); + + setup_address_with_balance(&mut platform, first, 0, first_balance); + setup_address_with_balance(&mut platform, second, 0, second_balance); + setup_address_with_balance(&mut platform, third, 0, third_balance); + + let total_transfer = first_input + second_input + third_input; + + let mut inputs = BTreeMap::new(); + inputs.insert(first, (1 as AddressNonce, first_input)); + inputs.insert(second, (1 as AddressNonce, second_input)); + inputs.insert(third, (1 as AddressNonce, third_input)); + + let mut outputs = BTreeMap::new(); + outputs.insert(create_platform_address(100), total_transfer); + + // Fee strategy: deduct from index 0 (first), then index 1 (should be second). + // Bug: after first is drained and removed, index 1 becomes third. + let fee_strategy = vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + AddressFundsFeeStrategyStep::DeductFromInput(1), + ]; + + let transition = create_signed_transition_with_custom_outputs( + &signer, + inputs, + outputs, + fee_strategy, + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction should succeed" + ); + + // Commit to read final balances + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + // Check that fees were deducted from SECOND (index 1), not THIRD. + // remaining_before_fee for second = second_balance - second_input + let second_remaining_before_fee = second_balance - second_input; + + let (_, second_final) = platform + .drive + .fetch_balance_and_nonce(&second, None, platform_version) + .expect("should fetch") + .expect("second address should exist"); + + // If fee was correctly deducted from second (BTreeMap index 1 before mutation), + // second_final should be LESS than second_remaining_before_fee. + // With the bug, second is untouched and third gets the fee instead. + assert!( + second_final < second_remaining_before_fee, + "AUDIT M1: Fee should have been deducted from second address (original \ + BTreeMap index 1), but it was deducted from third address instead. \ + After first was drained (1000 credits) and removed from BTreeMap, \ + DeductFromInput(1) shifted to target the third address. \ + second's balance: {} (expected < {})", + second_final, + second_remaining_before_fee + ); + } + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs index 57e7db27fe7..90d7f68499d 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_addresses/mod.rs @@ -2,6 +2,8 @@ mod advanced_structure; mod basic_structure; pub(crate) mod public_key_signatures; mod state; +#[cfg(test)] +mod tests; use crate::error::execution::ExecutionError; use crate::error::Error; 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 76d15b21ac4..7da09b71e4d 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 @@ -603,10 +603,9 @@ mod tests { // and fails when trying to validate the missing second witness assert_matches!( check_result.errors.as_slice(), - [ConsensusError::SignatureError(_)] - | [ConsensusError::BasicError( - BasicError::InputWitnessCountMismatchError(_) - )] + [ConsensusError::SignatureError( + SignatureError::InvalidStateTransitionSignatureError(_) + )] ); } @@ -1036,7 +1035,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); } @@ -1135,7 +1134,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); } @@ -1240,7 +1239,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); } } @@ -1323,7 +1322,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); // Commit the transaction @@ -1441,7 +1440,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); // Commit the transaction @@ -1538,7 +1537,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); // Commit the transaction @@ -1898,7 +1897,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); // Commit the first transaction @@ -2035,7 +2034,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); // Commit the first transaction @@ -2294,7 +2293,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }] + [StateTransitionExecutionResult::SuccessfulExecution { .. }] ); // Verify the identity was actually created and funded @@ -2799,7 +2798,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }], + [StateTransitionExecutionResult::SuccessfulExecution { .. }], "Expected valid structure, got {:?}", processing_result.execution_results() ); @@ -2997,7 +2996,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }], + [StateTransitionExecutionResult::SuccessfulExecution { .. }], "Expected valid structure with output, got {:?}", processing_result.execution_results() ); @@ -3595,7 +3594,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }], + [StateTransitionExecutionResult::SuccessfulExecution { .. }], "Expected valid structure with single master key, got {:?}", processing_result.execution_results() ); @@ -3670,7 +3669,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }], + [StateTransitionExecutionResult::SuccessfulExecution { .. }], "Expected valid structure with multiple keys, got {:?}", processing_result.execution_results() ); @@ -3745,7 +3744,7 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), - [StateTransitionExecutionResult::SuccessfulExecution{ .. }], + [StateTransitionExecutionResult::SuccessfulExecution { .. }], "Expected valid structure with max keys, got {:?}", processing_result.execution_results() ); @@ -8840,8 +8839,13 @@ mod tests { dash_to_credits!(10.0), ); - // Set up output address with near-max balance - setup_address_with_balance(&mut platform, output_address.clone(), 0, u64::MAX - 1000); + // Set up output address with near-max balance (leave room for other balances in sum tree) + setup_address_with_balance( + &mut platform, + output_address.clone(), + 0, + i64::MAX as u64 - dash_to_credits!(1000.0), + ); let mut inputs = BTreeMap::new(); inputs.insert( @@ -11316,4 +11320,400 @@ mod tests { ); } } + + mod security { + use super::*; + use dpp::state_transition::StateTransitionStructureValidation; + + /// AUDIT M1: Fee deduction BTreeMap index shifting after entry removal. + /// + /// When fee strategy step DeductFromInput(0) drains input A to zero, + /// A is removed from the BTreeMap. The next step DeductFromInput(1) + /// now targets what was originally at index 2 (C) instead of index 1 (B), + /// because all indices shifted down after the removal. + /// + /// Location: rs-dpp/.../deduct_fee_from_inputs_and_outputs/v0/mod.rs:35-45 + #[test] + fn test_fee_deduction_stable_after_entry_removal() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut rng = StdRng::seed_from_u64(567); + + // Create identity + let (identity, identity_signer) = + create_identity_with_keys([1u8; 32], &mut rng, platform_version); + + let mut address_signer = TestAddressSigner::new(); + let addr_a = address_signer.add_p2pkh([10u8; 32]); + let addr_b = address_signer.add_p2pkh([20u8; 32]); + let addr_c = address_signer.add_p2pkh([30u8; 32]); + + // Determine BTreeMap sort order + let mut sorted_addrs = vec![addr_a, addr_b, addr_c]; + sorted_addrs.sort(); + let first = sorted_addrs[0]; + let second = sorted_addrs[1]; + let third = sorted_addrs[2]; + + let first_balance = dash_to_credits!(0.1); + let second_balance = dash_to_credits!(1.0); + let third_balance = dash_to_credits!(1.0); + + // Input amount leaves only 1000 credits remaining for first + let first_input = first_balance - 1000; + let second_input = dash_to_credits!(0.01); + let third_input = dash_to_credits!(0.01); + + setup_address_with_balance(&mut platform, first, 0, first_balance); + setup_address_with_balance(&mut platform, second, 0, second_balance); + setup_address_with_balance(&mut platform, third, 0, third_balance); + + let mut inputs = BTreeMap::new(); + inputs.insert(first, (1 as AddressNonce, first_input)); + inputs.insert(second, (1 as AddressNonce, second_input)); + inputs.insert(third, (1 as AddressNonce, third_input)); + + // Fee strategy: deduct from index 0 (first), then index 1 (should be second). + // Bug: after first is drained and removed, index 1 becomes third. + let fee_strategy = AddressFundsFeeStrategy::from(vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + AddressFundsFeeStrategyStep::DeductFromInput(1), + ]); + + let transition = create_signed_identity_create_from_addresses_transition_full( + &identity, + &address_signer, + &identity_signer, + inputs, + None, // No output + fee_strategy, + platform_version, + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction should succeed" + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + // Verify first address was fully drained (exercising the index-shift scenario) + let first_result = platform + .drive + .fetch_balance_and_nonce(&first, None, platform_version) + .expect("should fetch"); + assert!( + first_result.is_none() || first_result.map(|(_, b)| b) == Some(0), + "First address should be fully drained to exercise index-shift scenario" + ); + + let second_remaining_before_fee = second_balance - second_input; + + let (_, second_final) = platform + .drive + .fetch_balance_and_nonce(&second, None, platform_version) + .expect("should fetch") + .expect("second address should exist"); + + assert!( + second_final < second_remaining_before_fee, + "AUDIT M1: Fee should have been deducted from second address (original \ + BTreeMap index 1), but it was deducted from third address instead. \ + After first was drained (1000 credits) and removed from BTreeMap, \ + DeductFromInput(1) shifted to target the third address. \ + second's balance: {} (expected < {})", + second_final, + second_remaining_before_fee + ); + } + + /// AUDIT M3: Unchecked subtraction in identity_create_from_addresses transformer. + /// + /// At `transformer.rs:39`, the transformer uses `.sum()` (wrapping) and at + /// line 43 uses unchecked subtraction for computing the amount to fund the + /// new identity. If structure validation is bypassed, these operations could + /// wrap/underflow silently. + /// + /// This test verifies that structure validation catches overflow at the + /// correct level, but notes the transformer lacks defense-in-depth. + /// + /// Location: rs-drive/.../identity_create_from_addresses/v0/transformer.rs:39,43 + #[test] + fn test_transformer_subtraction_uses_checked_arithmetic() { + use crate::execution::validation::state_transition::processor::traits::basic_structure::StateTransitionBasicStructureValidationV0; + + let platform_version = PlatformVersion::latest(); + let mut rng = StdRng::seed_from_u64(999); + + let public_keys = create_default_public_keys(&mut rng, platform_version); + + // Two inputs that sum to > u64::MAX + let mut inputs = BTreeMap::new(); + inputs.insert(create_platform_address(1), (0 as AddressNonce, u64::MAX)); + inputs.insert(create_platform_address(2), (0 as AddressNonce, u64::MAX)); + + let transition = create_raw_transition_with_dummy_witnesses( + public_keys, + inputs, + None, + AddressFundsFeeStrategy::from(vec![AddressFundsFeeStrategyStep::DeductFromInput( + 0, + )]), + 2, + ); + + let result = transition + .validate_basic_structure(dpp::dashcore::Network::Testnet, platform_version) + .expect("validation should not return Err"); + + assert!(!result.is_valid()); + assert_matches!( + result.first_error().unwrap(), + ConsensusError::BasicError(BasicError::OverflowError(_)) + ); + } + + /// AUDIT M8: Fee deduction doesn't check fee_fully_covered. + /// + /// When processing IdentityCreateFromAddresses, the execution path deducts + /// fees but never checks whether `fee_fully_covered` is true. If the actual + /// fee exceeds the estimated fee, partial payment occurs — the validator + /// subsidizes the difference. + /// + /// This test creates a transition where address balances are just barely + /// enough for the transfer amount but insufficient to cover fees. + /// + /// Location: rs-drive-abci/.../identity_create_from_addresses (fee deduction logic) + #[test] + fn test_partial_fee_payment_rejected() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut rng = StdRng::seed_from_u64(567); + + let (identity, identity_signer) = + create_identity_with_keys([1u8; 32], &mut rng, platform_version); + + let mut address_signer = TestAddressSigner::new(); + let input_address = address_signer.add_p2pkh([1u8; 32]); + + // Set up address with a very small balance — just enough for a tiny transfer + // but not enough to also cover processing fees + let min_output = platform_version + .dpp + .state_transitions + .address_funds + .min_output_amount; + let tiny_balance = min_output + 1; // Just barely above minimum + + setup_address_with_balance(&mut platform, input_address, 0, tiny_balance); + + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (1 as AddressNonce, tiny_balance)); + + // All input goes to identity creation, nothing left for fees + let transition = create_signed_identity_create_from_addresses_transition( + &identity, + &address_signer, + &identity_signer, + inputs, + None, + Some(AddressFundsFeeStrategy::from(vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + ])), + platform_version, + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + // The transition should either: + // 1. Be rejected because insufficient funds for fees, OR + // 2. Succeed with fees fully deducted from the input + // + // What it should NOT do is succeed with partial fee payment. + // The fee_fully_covered flag should be checked. + match processing_result.execution_results().as_slice() { + [StateTransitionExecutionResult::SuccessfulExecution { .. }] => { + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + // If it succeeded, verify the input was fully consumed + let (_, input_final) = platform + .drive + .fetch_balance_and_nonce(&input_address, None, platform_version) + .expect("should fetch") + .unwrap_or((0, 0)); + + // The input address should have 0 balance (all consumed) + // If fee_fully_covered was not checked, the input might still + // have some balance because fees were only partially deducted. + assert!( + input_final == 0, + "AUDIT M8: Identity creation succeeded but input address still has \ + {} credits remaining. This suggests fees were not fully deducted — \ + fee_fully_covered was not checked. The validator subsidized the \ + remaining fee cost.", + input_final, + ); + } + _ => { + // Rejected — acceptable behavior (insufficient funds for fees) + } + } + } + + /// AUDIT L4: Identity ID derivation lacks domain separator. + /// + /// `identity_id_from_inputs()` derives identity ID from input addresses and nonces + /// using double hashing, but does NOT include the transition type in the hash. + /// This means different transition types with identical inputs would produce + /// the same identity ID, creating potential cross-transition collisions. + /// + /// Location: rs-dpp/.../state_transition_identity_id_from_inputs.rs + #[test] + fn test_identity_id_has_no_domain_separator() { + use dpp::state_transition::StateTransitionIdentityIdFromInputs; + + let platform_version = PlatformVersion::latest(); + let mut rng = StdRng::seed_from_u64(567); + + // Create an identity and address signer + let (identity, identity_signer) = + create_identity_with_keys([1u8; 32], &mut rng, platform_version); + + let mut address_signer = TestAddressSigner::new(); + let addr = address_signer.add_p2pkh([1u8; 32]); + + let mut inputs = BTreeMap::new(); + inputs.insert(addr, (1 as AddressNonce, dash_to_credits!(1.0))); + + // Create an IdentityCreateFromAddresses transition + let transition = create_signed_identity_create_from_addresses_transition( + &identity, + &address_signer, + &identity_signer, + inputs.clone(), + None, + None, + platform_version, + ); + + // Get the identity ID from the transition + let create_id = match &transition { + StateTransition::IdentityCreateFromAddresses(t) => t + .identity_id_from_inputs() + .expect("should derive identity ID"), + _ => panic!("Expected IdentityCreateFromAddresses"), + }; + + // The identity ID is derived purely from input addresses and nonces. + // If a different transition type (e.g., IdentityTopUpFromAddresses) used + // the same inputs, it would produce the same ID because no transition + // type discriminator is included in the hash. + // + // We verify the ID is deterministic (same inputs → same ID) + let transition2 = create_signed_identity_create_from_addresses_transition( + &identity, + &address_signer, + &identity_signer, + inputs, + None, + None, + platform_version, + ); + + let create_id_2 = match &transition2 { + StateTransition::IdentityCreateFromAddresses(t) => t + .identity_id_from_inputs() + .expect("should derive identity ID"), + _ => panic!("Expected IdentityCreateFromAddresses"), + }; + + // Same inputs → same ID (deterministic) + assert_eq!( + create_id, create_id_2, + "Identity ID should be deterministic for same inputs" + ); + + // The vulnerability: the hash does NOT include the transition type. + // If someone creates a different transition type with the same inputs, + // they get the same identity ID. This is documented as a low-severity + // issue since the practical impact is limited (different transition types + // are processed differently and the collision doesn't lead to exploits + // in the current codebase). + // + // To fix: include a domain separator (transition type byte) in the hash input. + // e.g., hash(0x01 || address_nonce_data) for creates + // hash(0x02 || address_nonce_data) for topups + } + } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer_to_addresses/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer_to_addresses/tests.rs index 5d099279aac..f98f0d1e10e 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer_to_addresses/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer_to_addresses/tests.rs @@ -234,6 +234,198 @@ mod tests { } } + mod security { + use super::*; + use dpp::state_transition::StateTransitionStructureValidation; + + /// AUDIT M9: Unchecked `.sum()` wrapping in drive operations. + /// + /// At `identity_credit_transfer_to_addresses_transition.rs:38`, + /// `recipient_addresses.values().sum()` uses wrapping arithmetic. + /// If the sum overflows, `RemoveFromIdentityBalance` removes a tiny + /// amount while each recipient gets credited fully — credits from nothing. + /// + /// Structure validation at the DPP layer uses `checked_add` and catches + /// this, but the drive operation lacks its own defense-in-depth check. + /// + /// This test verifies structure validation catches the overflow. + /// + /// Location: rs-drive/.../identity_credit_transfer_to_addresses_transition.rs:38 + #[test] + fn test_recipient_sum_overflow_returns_error() { + let platform_version = PlatformVersion::latest(); + + // Create two recipients whose amounts sum to > u64::MAX + let mut recipient_addresses = BTreeMap::new(); + recipient_addresses.insert(create_platform_address(1), u64::MAX); + recipient_addresses.insert(create_platform_address(2), u64::MAX); + + let transition_v0 = IdentityCreditTransferToAddressesTransitionV0 { + identity_id: [1u8; 32].into(), + recipient_addresses, + nonce: 1, + user_fee_increase: 0, + signature_public_key_id: 0, + signature: BinaryData::new(vec![0; 65]), + }; + + // Structure validation should catch the overflow + let result = transition_v0.validate_structure(platform_version); + + assert!( + !result.is_valid(), + "AUDIT M9: Two recipients of u64::MAX should cause overflow in structure \ + validation. The DPP layer uses checked_add and catches this. However, the \ + drive operation at identity_credit_transfer_to_addresses_transition.rs:38 \ + uses .sum() which would silently wrap, allowing credit creation from nothing." + ); + + let has_overflow = result + .errors + .iter() + .any(|e| matches!(e, ConsensusError::BasicError(BasicError::OverflowError(_)))); + assert!( + has_overflow, + "AUDIT M9: Expected OverflowError, got {:?}", + result.errors + ); + } + + /// AUDIT L6: transform_into_action performs zero validation. + /// + /// `transform_into_action_v0` at `transform_into_action/v0/mod.rs:16-23` + /// simply wraps the transition into an action with zero checks. No balance + /// validation, no nonce validation, no recipient validation occurs in the + /// transformer — it relies entirely on upstream and downstream checks. + /// + /// This test demonstrates that any data passes through unchecked. + /// + /// Location: rs-drive-abci/.../identity_credit_transfer_to_addresses/transform_into_action/v0/mod.rs:16-23 + #[test] + fn test_transform_into_action_passes_without_validation() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let mut rng = StdRng::seed_from_u64(567); + + // Create identity with zero balance + let (identity, signer) = create_identity_with_transfer_key( + [1u8; 32], + 0, // Zero balance + &mut rng, + platform_version, + ); + + add_identity_to_drive(&mut platform, &identity); + + // Create transfer that exceeds balance (identity has 0) + let mut recipient_addresses = BTreeMap::new(); + recipient_addresses.insert(create_platform_address(1), dash_to_credits!(1.0)); + + let transition = create_signed_transition(&identity, &signer, recipient_addresses, 1); + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process"); + + // The transition should be rejected due to insufficient balance. + // This rejection happens at the drive operation level, NOT in transform_into_action. + // transform_into_action passes it through with zero validation. + // This test documents that the transformer is a pure pass-through. + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::StateError(StateError::IdentityInsufficientBalanceError(_)) + )] + ); + } + + /// AUDIT L8: Fee estimation uses saturating_add while structure uses checked_add. + /// + /// Fee estimation at `state_transition_estimated_fee_validation.rs:19` uses + /// `saturating_add` when computing the total. Structure validation uses + /// `checked_add`. With very large values, fee estimation silently saturates + /// (to u64::MAX) instead of erroring, while structure validation properly + /// errors with OverflowError. This inconsistency could allow a malformed + /// transition to pass fee estimation but fail structure validation. + /// + /// Location: rs-drive-abci/.../state_transition_estimated_fee_validation.rs:19 + #[test] + fn test_fee_estimation_saturating_add_matches_structure_validation() { + let platform_version = PlatformVersion::latest(); + + // Create recipients whose sum overflows u64 + let mut recipient_addresses = BTreeMap::new(); + recipient_addresses.insert(create_platform_address(1), u64::MAX - 1); + recipient_addresses.insert(create_platform_address(2), u64::MAX - 1); + + let transition_v0 = IdentityCreditTransferToAddressesTransitionV0 { + identity_id: [1u8; 32].into(), + recipient_addresses: recipient_addresses.clone(), + nonce: 1, + user_fee_increase: 0, + signature_public_key_id: 0, + signature: BinaryData::new(vec![0; 65]), + }; + + // Structure validation should reject with OverflowError (uses checked_add) + let structure_result = transition_v0.validate_structure(platform_version); + assert!( + !structure_result.is_valid(), + "Structure validation should reject overflow" + ); + + let has_overflow = structure_result + .errors + .iter() + .any(|e| matches!(e, ConsensusError::BasicError(BasicError::OverflowError(_)))); + + // The key insight: structure validation uses checked_add (correct), + // but fee estimation uses saturating_add (inconsistent). Fee estimation + // would compute a saturated sum = u64::MAX and proceed without error, + // while structure validation correctly rejects. + // + // This inconsistency means the fee estimation path and structure validation + // path have different behavior for the same input — a code smell that could + // mask issues if the order of validation changes. + assert!( + has_overflow, + "AUDIT L8: Structure validation correctly rejects with OverflowError \ + (uses checked_add). However, fee estimation at \ + state_transition_estimated_fee_validation.rs:19 uses saturating_add, \ + which would silently compute u64::MAX instead of erroring. \ + This inconsistency means fee estimation and structure validation \ + disagree on overflow handling. Got errors: {:?}", + structure_result.errors + ); + } + } + // ========================================== // SUCCESSFUL TRANSITION TESTS // ========================================== diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs index 7d0d9b1a7f7..ba5c693a780 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs @@ -11,7 +11,7 @@ use dpp::state_transition::identity_credit_withdrawal_transition::accessors::Ide use dpp::state_transition::identity_credit_withdrawal_transition::{ IdentityCreditWithdrawalTransition, MIN_CORE_FEE_PER_BYTE, MIN_WITHDRAWAL_AMOUNT, }; -use dpp::util::is_fibonacci_number::is_fibonacci_number; +use dpp::util::is_non_zero_fibonacci_number::is_non_zero_fibonacci_number; use dpp::validation::SimpleConsensusValidationResult; use dpp::version::PlatformVersion; use dpp::withdrawal::Pooling; @@ -53,7 +53,7 @@ impl IdentityCreditWithdrawalStateTransitionStructureValidationV1 } // validate core_fee is in fibonacci sequence - if !is_fibonacci_number(self.core_fee_per_byte() as u64) { + if !is_non_zero_fibonacci_number(self.core_fee_per_byte() as u64) { result.add_error(InvalidCreditWithdrawalTransitionCoreFeeError::new( self.core_fee_per_byte(), MIN_CORE_FEE_PER_BYTE, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up_from_addresses/tests.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up_from_addresses/tests.rs index a3c8342abe1..fbf1d17bf99 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up_from_addresses/tests.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_top_up_from_addresses/tests.rs @@ -2,8 +2,8 @@ mod tests { use crate::config::{PlatformConfig, PlatformTestConfig}; use crate::execution::validation::state_transition::state_transitions::test_helpers::{ - create_dummy_witness, setup_address_with_balance, TestAddressSigner, - TestProtocolError as ProtocolError, + create_dummy_witness, create_platform_address, setup_address_with_balance, + TestAddressSigner, TestProtocolError as ProtocolError, }; use crate::platform_types::state_transitions_processing_result::StateTransitionExecutionResult; use crate::test::helpers::setup::TestPlatformBuilder; @@ -13,6 +13,7 @@ mod tests { }; use dpp::block::block_info::BlockInfo; use dpp::consensus::basic::BasicError; + use dpp::consensus::signature::SignatureError; use dpp::consensus::state::state_error::StateError; use dpp::consensus::ConsensusError; use dpp::dash_to_credits; @@ -1045,7 +1046,9 @@ mod tests { assert_matches!( processing_result.execution_results().as_slice(), [StateTransitionExecutionResult::UnpaidConsensusError( - ConsensusError::BasicError(BasicError::InputWitnessCountMismatchError(_)) + ConsensusError::SignatureError( + SignatureError::InvalidStateTransitionSignatureError(_) + ) )] ); } @@ -3280,4 +3283,174 @@ mod tests { )); } } + + mod security { + use super::*; + + /// AUDIT M1: Fee deduction BTreeMap index shifting after entry removal. + /// + /// When fee strategy step DeductFromInput(0) drains input A to zero, + /// A is removed from the BTreeMap. The next step DeductFromInput(1) + /// now targets what was originally at index 2 (C) instead of index 1 (B), + /// because all indices shifted down after the removal. + /// + /// Location: rs-dpp/.../deduct_fee_from_inputs_and_outputs/v0/mod.rs:35-45 + #[test] + fn test_fee_deduction_stable_after_entry_removal() { + let platform_version = PlatformVersion::latest(); + let platform_config = PlatformConfig { + testing_configs: PlatformTestConfig { + disable_instant_lock_signature_verification: true, + ..Default::default() + }, + ..Default::default() + }; + + let mut platform = TestPlatformBuilder::new() + .with_config(platform_config) + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_genesis_state(); + + let identity = setup_identity(&mut platform, 1, dash_to_credits!(1.0)); + + let mut signer = TestAddressSigner::new(); + let addr_a = signer.add_p2pkh([10u8; 32]); + let addr_b = signer.add_p2pkh([20u8; 32]); + let addr_c = signer.add_p2pkh([30u8; 32]); + + // Determine BTreeMap sort order + let mut sorted_addrs = vec![addr_a, addr_b, addr_c]; + sorted_addrs.sort(); + let first = sorted_addrs[0]; + let second = sorted_addrs[1]; + let third = sorted_addrs[2]; + + let first_balance = dash_to_credits!(0.1); + let second_balance = dash_to_credits!(1.0); + let third_balance = dash_to_credits!(1.0); + + // Input amount leaves only 1000 credits remaining for first + let first_input = first_balance - 1000; + let second_input = dash_to_credits!(0.01); + let third_input = dash_to_credits!(0.01); + + setup_address_with_balance(&mut platform, first, 0, first_balance); + setup_address_with_balance(&mut platform, second, 0, second_balance); + setup_address_with_balance(&mut platform, third, 0, third_balance); + + let mut inputs = BTreeMap::new(); + inputs.insert(first, (1 as AddressNonce, first_input)); + inputs.insert(second, (1 as AddressNonce, second_input)); + inputs.insert(third, (1 as AddressNonce, third_input)); + + // Fee strategy: deduct from index 0 (first), then index 1 (should be second). + let fee_strategy = AddressFundsFeeStrategy::from(vec![ + AddressFundsFeeStrategyStep::DeductFromInput(0), + AddressFundsFeeStrategyStep::DeductFromInput(1), + ]); + + let transition = create_signed_transition_with_options( + &identity, + &signer, + inputs, + None, + fee_strategy, + 0, + platform_version, + ); + + let result = transition.serialize_to_bytes().expect("should serialize"); + + let platform_state = platform.state.load(); + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &vec![result], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution { .. }], + "Transaction should succeed" + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("should commit"); + + let second_remaining_before_fee = second_balance - second_input; + + let (_, second_final) = platform + .drive + .fetch_balance_and_nonce(&second, None, platform_version) + .expect("should fetch") + .expect("second address should exist"); + + assert!( + second_final < second_remaining_before_fee, + "AUDIT M1: Fee should have been deducted from second address (original \ + BTreeMap index 1), but it was deducted from third address instead. \ + After first was drained (1000 credits) and removed from BTreeMap, \ + DeductFromInput(1) shifted to target the third address. \ + second's balance: {} (expected < {})", + second_final, + second_remaining_before_fee + ); + } + + /// AUDIT M3: Unchecked subtraction in identity_top_up_from_addresses transformer. + /// + /// At `transformer.rs:24`, the transformer uses `.sum()` (wrapping) and at + /// line 28 uses unchecked subtraction. If structure validation is bypassed, + /// these operations could wrap/underflow silently. + /// + /// This test verifies structure validation catches overflow, but notes + /// the transformer lacks defense-in-depth. + /// + /// Location: rs-drive/.../identity_top_up_from_addresses/v0/transformer.rs:24,28 + #[test] + fn test_transformer_subtraction_uses_checked_arithmetic() { + use crate::execution::validation::state_transition::processor::traits::basic_structure::StateTransitionBasicStructureValidationV0; + + let platform_version = PlatformVersion::latest(); + + // Two inputs that sum to > u64::MAX + let mut inputs = BTreeMap::new(); + inputs.insert(create_platform_address(1), (0 as AddressNonce, u64::MAX)); + inputs.insert(create_platform_address(2), (0 as AddressNonce, u64::MAX)); + + let transition = create_raw_transition_with_dummy_witnesses( + Identifier::random(), + inputs, + None, + AddressFundsFeeStrategy::from(vec![AddressFundsFeeStrategyStep::DeductFromInput( + 0, + )]), + 2, + ); + + let result = transition + .validate_basic_structure(dpp::dashcore::Network::Testnet, platform_version) + .expect("validation should not return Err"); + + assert!(!result.is_valid()); + assert_matches!( + result.first_error().unwrap(), + ConsensusError::BasicError(BasicError::OverflowError(_)) + ); + } + } } diff --git a/packages/rs-drive/src/drive/address_funds/set_balance_to_address/v0/mod.rs b/packages/rs-drive/src/drive/address_funds/set_balance_to_address/v0/mod.rs index 16be97c5025..281a859fd62 100644 --- a/packages/rs-drive/src/drive/address_funds/set_balance_to_address/v0/mod.rs +++ b/packages/rs-drive/src/drive/address_funds/set_balance_to_address/v0/mod.rs @@ -4,6 +4,7 @@ use crate::fees::op::LowLevelDriveOperation; use dpp::address_funds::PlatformAddress; use dpp::fee::Credits; use dpp::prelude::AddressNonce; +use dpp::ProtocolError; use grovedb::batch::KeyInfoPath; use grovedb::element::SumValue; use grovedb::{Element, EstimatedLayerInformation}; @@ -44,6 +45,13 @@ impl Drive { )?; } + // Guard against u64 values that would overflow when cast to i64 (SumValue) + if balance > i64::MAX as u64 { + return Err( + ProtocolError::Overflow("balance exceeds maximum for sum tree storage").into(), + ); + } + // Simply insert/overwrite the balance as an ItemWithSumItem element // The nonce is stored as big-endian bytes, and the balance is the sum value Ok(vec![ diff --git a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/address_funds/address_funding_from_asset_lock_transition.rs b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/address_funds/address_funding_from_asset_lock_transition.rs index db5777a739f..30f85a501f4 100644 --- a/packages/rs-drive/src/state_transition_action/action_convert_to_operations/address_funds/address_funding_from_asset_lock_transition.rs +++ b/packages/rs-drive/src/state_transition_action/action_convert_to_operations/address_funds/address_funding_from_asset_lock_transition.rs @@ -25,7 +25,7 @@ impl DriveHighLevelOperationConverter for AddressFundingFromAssetLockTransitionA 0 => { let asset_lock_outpoint = self.asset_lock_outpoint(); - let (inputs, outputs, mut asset_lock_value) = + let (inputs, outputs, mut asset_lock_value, input_contributions_total) = self.inputs_with_remaining_balance_outputs_and_asset_lock_value_owned(); let initial_balance = asset_lock_value.remaining_credit_value(); @@ -56,9 +56,12 @@ impl DriveHighLevelOperationConverter for AddressFundingFromAssetLockTransitionA // Calculate the sum of explicit outputs let explicit_outputs_sum: u64 = outputs.values().flatten().sum(); - // Calculate remainder: initial_balance - explicit_outputs_sum - // Note: fees are handled separately during validation, inputs have been consumed above - let remainder_balance = initial_balance.saturating_sub(explicit_outputs_sum); + // Total available for outputs = asset lock + input contributions + // (input credits are already in the system — we're moving them, not creating them) + let total_available = initial_balance + input_contributions_total; + + // Calculate remainder: total_available - explicit_outputs_sum + let remainder_balance = total_available.saturating_sub(explicit_outputs_sum); // Add balance to outputs for (address, balance_option) in outputs { diff --git a/packages/rs-drive/src/state_transition_action/address_funds/address_credit_withdrawal/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/address_funds/address_credit_withdrawal/v0/transformer.rs index 710239207b4..862b2d052c9 100644 --- a/packages/rs-drive/src/state_transition_action/address_funds/address_credit_withdrawal/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/address_funds/address_credit_withdrawal/v0/transformer.rs @@ -1,5 +1,6 @@ use crate::state_transition_action::address_funds::address_credit_withdrawal::v0::AddressCreditWithdrawalTransitionActionV0; use dpp::address_funds::PlatformAddress; +use dpp::consensus::basic::overflow_error::OverflowError; use dpp::data_contracts::withdrawals_contract; use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal; use dpp::document::{Document, DocumentV0}; @@ -32,12 +33,33 @@ impl AddressCreditWithdrawalTransitionActionV0 { .. } = value; - // Sum all balances from inputs - let total_inputs: Credits = inputs.values().map(|(_, balance)| *balance).sum(); + // Sum all balances from inputs (checked to prevent overflow) + let total_inputs: Credits = match inputs + .values() + .try_fold(0u64, |acc, (_, balance)| acc.checked_add(*balance)) + { + Some(sum) => sum, + None => { + return ConsensusValidationResult::new_with_error( + OverflowError::new("Input sum overflow in withdrawal transformer".to_string()) + .into(), + ) + } + }; // Calculate the withdrawal amount: total remaining minus output (if any) let amount = match output { - Some((_, output_amount)) => total_inputs - output_amount, + Some((_, output_amount)) => match total_inputs.checked_sub(*output_amount) { + Some(diff) => diff, + None => { + return ConsensusValidationResult::new_with_error( + OverflowError::new( + "Output exceeds input sum in withdrawal transformer".to_string(), + ) + .into(), + ) + } + }, None => total_inputs, }; diff --git a/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/mod.rs b/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/mod.rs index eb036d54b82..f706035c265 100644 --- a/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/mod.rs +++ b/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/mod.rs @@ -47,6 +47,15 @@ impl AddressFundingFromAssetLockTransitionAction { } } + /// Get total credits contributed from address inputs + pub fn input_contributions_total(&self) -> Credits { + match self { + AddressFundingFromAssetLockTransitionAction::V0(transition) => { + transition.input_contributions_total + } + } + } + /// Get resolved outputs with remainder computed. /// Returns outputs where all Option are resolved to concrete Credits values. pub fn resolved_outputs(&self) -> BTreeMap { @@ -57,11 +66,14 @@ impl AddressFundingFromAssetLockTransitionAction { .asset_lock_value_to_be_consumed() .remaining_credit_value(); + // Total available = asset lock + input contributions + let total_available = asset_lock_balance + self.input_contributions_total(); + // Calculate the sum of explicit outputs let explicit_outputs_sum: Credits = outputs.values().flatten().sum(); // Calculate remainder - let remainder_balance = asset_lock_balance.saturating_sub(explicit_outputs_sum); + let remainder_balance = total_available.saturating_sub(explicit_outputs_sum); // Resolve all outputs outputs @@ -76,7 +88,7 @@ impl AddressFundingFromAssetLockTransitionAction { .collect() } - /// Returns owned copies of inputs and outputs. + /// Returns owned copies of inputs, outputs, asset lock value, and input contributions total. #[allow(clippy::type_complexity)] pub fn inputs_with_remaining_balance_outputs_and_asset_lock_value_owned( self, @@ -84,12 +96,14 @@ impl AddressFundingFromAssetLockTransitionAction { BTreeMap, BTreeMap>, AssetLockValue, + Credits, ) { match self { AddressFundingFromAssetLockTransitionAction::V0(transition) => ( transition.inputs_with_remaining_balance, transition.outputs, transition.asset_lock_value_to_be_consumed, + transition.input_contributions_total, ), } } diff --git a/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/mod.rs b/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/mod.rs index 69c65a1a931..d2746aa61c1 100644 --- a/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/mod.rs +++ b/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/mod.rs @@ -21,6 +21,8 @@ pub struct AddressFundingFromAssetLockTransitionActionV0 { pub inputs_with_remaining_balance: BTreeMap, /// outputs (Some = explicit amount, None = remainder recipient) pub outputs: BTreeMap>, + /// Total credits contributed from address inputs (sum of amounts being spent) + pub input_contributions_total: Credits, /// fee strategy pub fee_strategy: AddressFundsFeeStrategy, /// fee multiplier diff --git a/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/transformer.rs index e136ef8cfe2..67e1114c0a9 100644 --- a/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/address_funds/address_funding_from_asset_lock/v0/transformer.rs @@ -38,11 +38,16 @@ impl AddressFundingFromAssetLockTransitionActionV0 { ) })?; + // Compute total credits contributed from address inputs + let input_contributions_total: Credits = + value.inputs.values().map(|(_, amount)| *amount).sum(); + Ok(AddressFundingFromAssetLockTransitionActionV0 { signable_bytes_hasher, asset_lock_value_to_be_consumed, asset_lock_outpoint: Bytes36::new(asset_lock_outpoint.into()), inputs_with_remaining_balance, + input_contributions_total, outputs: outputs.clone(), fee_strategy: fee_strategy.clone(), user_fee_increase: *user_fee_increase, diff --git a/packages/rs-drive/src/state_transition_action/identity/identity_create_from_addresses/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/identity/identity_create_from_addresses/v0/transformer.rs index dae4226184e..c6d98a4a6a1 100644 --- a/packages/rs-drive/src/state_transition_action/identity/identity_create_from_addresses/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/identity/identity_create_from_addresses/v0/transformer.rs @@ -1,5 +1,6 @@ use crate::state_transition_action::identity::identity_create_from_addresses::v0::IdentityCreateFromAddressesTransitionActionV0; use dpp::address_funds::PlatformAddress; +use dpp::consensus::basic::overflow_error::OverflowError; use dpp::consensus::basic::value_error::ValueError; use dpp::consensus::ConsensusError; use dpp::fee::Credits; @@ -35,12 +36,35 @@ impl IdentityCreateFromAddressesTransitionActionV0 { .. } = value; - // Sum all balances from inputs - let total_inputs: Credits = inputs.values().map(|(_, balance)| *balance).sum(); + // Sum all balances from inputs (checked to prevent overflow) + let total_inputs: Credits = match inputs + .values() + .try_fold(0u64, |acc, (_, balance)| acc.checked_add(*balance)) + { + Some(sum) => sum, + None => { + return ConsensusValidationResult::new_with_error( + OverflowError::new( + "Input sum overflow in identity create transformer".to_string(), + ) + .into(), + ) + } + }; // Subtract the output amount if present let fund_identity_amount = match output { - Some((_, output_amount)) => total_inputs - output_amount, + Some((_, output_amount)) => match total_inputs.checked_sub(*output_amount) { + Some(diff) => diff, + None => { + return ConsensusValidationResult::new_with_error( + OverflowError::new( + "Output exceeds input sum in identity create transformer".to_string(), + ) + .into(), + ) + } + }, None => total_inputs, }; diff --git a/packages/rs-drive/src/state_transition_action/identity/identity_topup_from_addresses/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/identity/identity_topup_from_addresses/v0/transformer.rs index fd4321b738e..1fa5743f8a1 100644 --- a/packages/rs-drive/src/state_transition_action/identity/identity_topup_from_addresses/v0/transformer.rs +++ b/packages/rs-drive/src/state_transition_action/identity/identity_topup_from_addresses/v0/transformer.rs @@ -1,5 +1,6 @@ use crate::state_transition_action::identity::identity_topup_from_addresses::v0::IdentityTopUpFromAddressesTransitionActionV0; use dpp::address_funds::PlatformAddress; +use dpp::consensus::basic::overflow_error::OverflowError; use dpp::fee::Credits; use dpp::prelude::{AddressNonce, ConsensusValidationResult}; use dpp::state_transition::state_transitions::identity::identity_topup_from_addresses_transition::v0::IdentityTopUpFromAddressesTransitionV0; @@ -20,12 +21,35 @@ impl IdentityTopUpFromAddressesTransitionActionV0 { .. } = value; - // Sum all balances from inputs - let total_inputs: Credits = inputs.values().map(|(_, balance)| *balance).sum(); + // Sum all balances from inputs (checked to prevent overflow) + let total_inputs: Credits = match inputs + .values() + .try_fold(0u64, |acc, (_, balance)| acc.checked_add(*balance)) + { + Some(sum) => sum, + None => { + return ConsensusValidationResult::new_with_error( + OverflowError::new( + "Input sum overflow in identity topup transformer".to_string(), + ) + .into(), + ) + } + }; // Subtract the output amount if present to get the topup amount let topup_amount = match output { - Some((_, output_amount)) => total_inputs - output_amount, + Some((_, output_amount)) => match total_inputs.checked_sub(*output_amount) { + Some(diff) => diff, + None => { + return ConsensusValidationResult::new_with_error( + OverflowError::new( + "Output exceeds input sum in identity topup transformer".to_string(), + ) + .into(), + ) + } + }, None => total_inputs, }; diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 095a5b63a86..429a94b1a4f 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -88,7 +88,7 @@ use dpp::consensus::state::prefunded_specialized_balances::prefunded_specialized use dpp::consensus::state::prefunded_specialized_balances::prefunded_specialized_balance_not_found_error::PrefundedSpecializedBalanceNotFoundError; use dpp::consensus::state::token::{IdentityDoesNotHaveEnoughTokenBalanceError, IdentityTokenAccountNotFrozenError, IdentityTokenAccountFrozenError, TokenIsPausedError, IdentityTokenAccountAlreadyFrozenError, UnauthorizedTokenActionError, TokenSettingMaxSupplyToLessThanCurrentSupplyError, TokenMintPastMaxSupplyError, NewTokensDestinationIdentityDoesNotExistError, NewAuthorizedActionTakerIdentityDoesNotExistError, NewAuthorizedActionTakerGroupDoesNotExistError, NewAuthorizedActionTakerMainGroupNotSetError, InvalidGroupPositionError, TokenAlreadyPausedError, TokenNotPausedError, InvalidTokenClaimPropertyMismatch, InvalidTokenClaimNoCurrentRewards, InvalidTokenClaimWrongClaimant, TokenTransferRecipientIdentityNotExistError, PreProgrammedDistributionTimestampInPastError, IdentityHasNotAgreedToPayRequiredTokenAmountError, RequiredTokenPaymentInfoNotSetError, IdentityTryingToPayWithWrongTokenError, TokenDirectPurchaseUserPriceTooLow, TokenAmountUnderMinimumSaleAmount, TokenNotForDirectSale, InvalidTokenPositionStateError}; use dpp::consensus::state::address_funds::{AddressDoesNotExistError, AddressInvalidNonceError, AddressNotEnoughFundsError, AddressesNotEnoughFundsError}; -use dpp::consensus::basic::state_transition::{StateTransitionNotActiveError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, InputWitnessCountMismatchError, TransitionNoInputsError, TransitionNoOutputsError, FeeStrategyEmptyError, FeeStrategyDuplicateError, FeeStrategyIndexOutOfBoundsError, FeeStrategyTooManyStepsError, InputBelowMinimumError, OutputBelowMinimumError, InputOutputBalanceMismatchError, OutputsNotGreaterThanInputsError, WithdrawalBalanceMismatchError, InsufficientFundingAmountError, InputsNotLessThanOutputsError, OutputAddressAlsoInputError, InvalidRemainderOutputCountError}; +use dpp::consensus::basic::state_transition::{StateTransitionNotActiveError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, InputWitnessCountMismatchError, TransitionNoInputsError, TransitionNoOutputsError, FeeStrategyEmptyError, FeeStrategyDuplicateError, FeeStrategyIndexOutOfBoundsError, FeeStrategyTooManyStepsError, InputBelowMinimumError, OutputBelowMinimumError, InputOutputBalanceMismatchError, OutputsNotGreaterThanInputsError, WithdrawalBalanceMismatchError, InsufficientFundingAmountError, InputsNotLessThanOutputsError, OutputAddressAlsoInputError, InvalidRemainderOutputCountError, WithdrawalBelowMinAmountError}; use dpp::consensus::state::voting::masternode_incorrect_voter_identity_id_error::MasternodeIncorrectVoterIdentityIdError; use dpp::consensus::state::voting::masternode_incorrect_voting_address_error::MasternodeIncorrectVotingAddressError; use dpp::consensus::state::voting::masternode_not_found_error::MasternodeNotFoundError; @@ -920,6 +920,9 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::InvalidRemainderOutputCountError(e) => { generic_consensus_error!(InvalidRemainderOutputCountError, e).into() } + BasicError::WithdrawalBelowMinAmountError(e) => { + generic_consensus_error!(WithdrawalBelowMinAmountError, e).into() + } } } From 961fe9e1f5fac12e9a14b10d7f5a0199e1aff563 Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Thu, 5 Feb 2026 11:27:18 -0800 Subject: [PATCH 5/8] feat(platform)!: update PlatformAddress encoding and HRP constants (#3059) --- .../src/address_funds/platform_address.rs | 216 +++++++++++------- 1 file changed, 138 insertions(+), 78 deletions(-) diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index b577a5c5693..8d6f3e7a722 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -42,9 +42,13 @@ pub const ADDRESS_HASH_SIZE: usize = 20; )] #[platform_serialize(unversioned)] pub enum PlatformAddress { - /// Pay to pubkey hash (type byte = 0xb0) + /// Pay to pubkey hash + /// - bech32m encoding type byte: 0xb0 + /// - storage key type byte: 0x00 P2pkh([u8; 20]), - /// Pay to script hash (type byte = 0x80) + /// Pay to script hash + /// - bech32m encoding type byte: 0x80 + /// - storage key type byte: 0x01 P2sh([u8; 20]), } @@ -82,21 +86,21 @@ impl Default for PlatformAddress { } /// Human-readable part for Platform addresses on mainnet (DIP-0018) -pub const PLATFORM_HRP_MAINNET: &str = "evo"; +pub const PLATFORM_HRP_MAINNET: &str = "dash"; /// Human-readable part for Platform addresses on testnet/devnet/regtest (DIP-0018) -pub const PLATFORM_HRP_TESTNET: &str = "tevo"; +pub const PLATFORM_HRP_TESTNET: &str = "tdash"; impl PlatformAddress { - /// Type byte for P2PKH addresses + /// Type byte for P2PKH addresses in bech32m encoding (user-facing) pub const P2PKH_TYPE: u8 = 0xb0; - /// Type byte for P2SH addresses + /// Type byte for P2SH addresses in bech32m encoding (user-facing) pub const P2SH_TYPE: u8 = 0x80; /// Returns the appropriate HRP (Human-Readable Part) for the given network. /// /// Per DIP-0018: - /// - Mainnet: "evo" - /// - Testnet/Devnet/Regtest: "tevo" + /// - Mainnet: "dash" + /// - Testnet/Devnet/Regtest: "tdash" pub fn hrp_for_network(network: Network) -> &'static str { match network { Network::Dash => PLATFORM_HRP_MAINNET, @@ -113,18 +117,32 @@ impl PlatformAddress { /// - Data: type_byte (0xb0 for P2PKH, 0x80 for P2SH) || 20-byte hash /// - Checksum: bech32m (BIP-350) /// + /// NOTE: This uses bech32m type bytes (0xb0/0x80) for user-facing addresses, + /// NOT the storage type bytes (0x00/0x01) used in GroveDB keys. + /// /// # Example /// ```ignore /// let address = PlatformAddress::P2pkh([0xf7, 0xda, ...]); /// let encoded = address.to_bech32m_string(Network::Dash); - /// // Returns something like "evo1k..." + /// // Returns something like "dash1k..." /// ``` pub fn to_bech32m_string(&self, network: Network) -> String { let hrp_str = Self::hrp_for_network(network); let hrp = Hrp::parse(hrp_str).expect("HRP is valid"); // Build the 21-byte payload: type_byte || hash - let payload = self.to_bytes(); + // Using bech32m type bytes (0xb0/0x80), NOT storage type bytes (0x00/0x01) + let mut payload = Vec::with_capacity(1 + ADDRESS_HASH_SIZE); + match self { + PlatformAddress::P2pkh(hash) => { + payload.push(Self::P2PKH_TYPE); + payload.extend_from_slice(hash); + } + PlatformAddress::P2sh(hash) => { + payload.push(Self::P2SH_TYPE); + payload.extend_from_slice(hash); + } + } // Verified that this can not error bech32::encode::(hrp, &payload).expect("encoding should succeed") @@ -132,6 +150,9 @@ impl PlatformAddress { /// Decodes a bech32m-encoded Platform address string per DIP-0018. /// + /// NOTE: This expects bech32m type bytes (0xb0/0x80) in the encoded string, + /// NOT the storage type bytes (0x00/0x01) used in GroveDB keys. + /// /// # Returns /// - `Ok((PlatformAddress, Network))` - The decoded address and its network /// - `Err(ProtocolError)` - If the address is invalid @@ -162,8 +183,22 @@ impl PlatformAddress { ))); } - // Parse the address from bytes - Self::from_bytes(&data).map(|addr| (addr, network)) + // Parse using bech32m type bytes (0xb0/0x80), NOT storage type bytes + let address_type = data[0]; + let hash: [u8; 20] = data[1..21] + .try_into() + .map_err(|_| ProtocolError::DecodingError("invalid hash length".to_string()))?; + + let address = match address_type { + Self::P2PKH_TYPE => Ok(PlatformAddress::P2pkh(hash)), + Self::P2SH_TYPE => Ok(PlatformAddress::P2sh(hash)), + _ => Err(ProtocolError::DecodingError(format!( + "invalid address type: 0x{:02x}", + address_type + ))), + }?; + + Ok((address, network)) } /// Converts the PlatformAddress to a dashcore Address with the specified network. @@ -180,21 +215,14 @@ impl PlatformAddress { } } - /// Converts the PlatformAddress to bytes. - /// Format: [address_type (1 byte)] + [hash (20 bytes)] + /// Converts the PlatformAddress to bytes for storage keys. + /// Format: [variant_index (1 byte)] + [hash (20 bytes)] + /// + /// Uses bincode serialization which produces: 0x00 for P2pkh, 0x01 for P2sh. + /// These bytes are used as keys in GroveDB. pub fn to_bytes(&self) -> Vec { - let mut bytes = Vec::with_capacity(1 + ADDRESS_HASH_SIZE); - match self { - PlatformAddress::P2pkh(hash) => { - bytes.push(Self::P2PKH_TYPE); - bytes.extend_from_slice(hash); - } - PlatformAddress::P2sh(hash) => { - bytes.push(Self::P2SH_TYPE); - bytes.extend_from_slice(hash); - } - } - bytes + bincode::encode_to_vec(self, bincode::config::standard()) + .expect("PlatformAddress serialization cannot fail") } /// Gets a base64 string of the PlatformAddress concatenated with the nonce. @@ -209,30 +237,16 @@ impl PlatformAddress { STANDARD.encode(bytes) } - /// Creates a PlatformAddress from bytes. - /// Format: [address_type (1 byte)] + [hash (20 bytes)] + /// Creates a PlatformAddress from storage bytes. + /// Format: [variant_index (1 byte)] + [hash (20 bytes)] + /// + /// Uses bincode deserialization which expects: 0x00 for P2pkh, 0x01 for P2sh. pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < 1 + ADDRESS_HASH_SIZE { - return Err(ProtocolError::DecodingError(format!( - "cannot decode PlatformAddress: expected {} bytes, got {}", - 1 + ADDRESS_HASH_SIZE, - bytes.len() - ))); - } - - let address_type = bytes[0]; - let hash: [u8; 20] = bytes[1..21] - .try_into() - .map_err(|_| ProtocolError::DecodingError("invalid hash length".to_string()))?; - - match address_type { - Self::P2PKH_TYPE => Ok(PlatformAddress::P2pkh(hash)), - Self::P2SH_TYPE => Ok(PlatformAddress::P2sh(hash)), - _ => Err(ProtocolError::DecodingError(format!( - "invalid address type: {}", - address_type - ))), - } + let (address, _): (Self, usize) = + bincode::decode_from_slice(bytes, bincode::config::standard()).map_err(|e| { + ProtocolError::DecodingError(format!("cannot decode PlatformAddress: {}", e)) + })?; + Ok(address) } /// Returns the hash portion of the address (20 bytes) @@ -567,13 +581,13 @@ impl FromStr for PlatformAddress { /// Parses a bech32m-encoded Platform address string. /// - /// This accepts addresses with either mainnet ("evo") or testnet ("tevo") HRP. + /// This accepts addresses with either mainnet ("dash") or testnet ("tdash") HRP. /// The network information is discarded; use `from_bech32m_string` if you need /// to preserve the network. /// /// # Example /// ```ignore - /// let address: PlatformAddress = "evo1k...".parse()?; + /// let address: PlatformAddress = "dash1k...".parse()?; /// ``` fn from_str(s: &str) -> Result { Self::from_bech32m_string(s) @@ -1000,7 +1014,7 @@ mod tests { #[test] fn test_bech32m_p2pkh_mainnet_roundtrip() { - // Test P2PKH address roundtrip on mainnet using DIP-0018 Vector 1 + // Test P2PKH address roundtrip on mainnet let hash: [u8; 20] = [ 0xf7, 0xda, 0x0a, 0x2b, 0x5c, 0xbd, 0x4f, 0xf6, 0xbb, 0x2c, 0x4d, 0x89, 0xb6, 0x7d, 0x2f, 0x3f, 0xfe, 0xec, 0x05, 0x25, @@ -1010,10 +1024,10 @@ mod tests { // Encode to bech32m let encoded = address.to_bech32m_string(Network::Dash); - // Verify exact match against DIP-0018 test vector + // Verify exact encoding assert_eq!( - encoded, "evo1krma5z3ttj75la4m93xcndna9ullamq9y59dj9x7", - "Encoded address must match DIP-0018 Vector 1 mainnet" + encoded, "dash1krma5z3ttj75la4m93xcndna9ullamq9y5e9n5rs", + "P2PKH mainnet encoding mismatch" ); // Decode and verify roundtrip @@ -1025,7 +1039,7 @@ mod tests { #[test] fn test_bech32m_p2pkh_testnet_roundtrip() { - // Test P2PKH address roundtrip on testnet using DIP-0018 Vector 1 + // Test P2PKH address roundtrip on testnet let hash: [u8; 20] = [ 0xf7, 0xda, 0x0a, 0x2b, 0x5c, 0xbd, 0x4f, 0xf6, 0xbb, 0x2c, 0x4d, 0x89, 0xb6, 0x7d, 0x2f, 0x3f, 0xfe, 0xec, 0x05, 0x25, @@ -1035,10 +1049,10 @@ mod tests { // Encode to bech32m let encoded = address.to_bech32m_string(Network::Testnet); - // Verify exact match against DIP-0018 test vector + // Verify exact encoding assert_eq!( - encoded, "tevo1krma5z3ttj75la4m93xcndna9ullamq9y5rky7cg", - "Encoded address must match DIP-0018 Vector 1 testnet" + encoded, "tdash1krma5z3ttj75la4m93xcndna9ullamq9y5fzq2j7", + "P2PKH testnet encoding mismatch" ); // Decode and verify roundtrip @@ -1050,7 +1064,7 @@ mod tests { #[test] fn test_bech32m_p2sh_mainnet_roundtrip() { - // Test P2SH address roundtrip on mainnet using DIP-0018 P2SH vector + // Test P2SH address roundtrip on mainnet let hash: [u8; 20] = [ 0x43, 0xfa, 0x18, 0x3c, 0xf3, 0xfb, 0x6e, 0x9e, 0x7d, 0xc6, 0x2b, 0x69, 0x2a, 0xeb, 0x4f, 0xc8, 0xd8, 0x04, 0x56, 0x36, @@ -1060,10 +1074,10 @@ mod tests { // Encode to bech32m let encoded = address.to_bech32m_string(Network::Dash); - // Verify exact match against DIP-0018 P2SH test vector + // Verify exact encoding assert_eq!( - encoded, "evo1sppl5xpu70aka8nacc4kj2htflydspzkxctaevg5", - "Encoded address must match DIP-0018 P2SH mainnet" + encoded, "dash1sppl5xpu70aka8nacc4kj2htflydspzkxch4cad6", + "P2SH mainnet encoding mismatch" ); // Decode and verify roundtrip @@ -1075,7 +1089,7 @@ mod tests { #[test] fn test_bech32m_p2sh_testnet_roundtrip() { - // Test P2SH address roundtrip on testnet using DIP-0018 P2SH vector + // Test P2SH address roundtrip on testnet let hash: [u8; 20] = [ 0x43, 0xfa, 0x18, 0x3c, 0xf3, 0xfb, 0x6e, 0x9e, 0x7d, 0xc6, 0x2b, 0x69, 0x2a, 0xeb, 0x4f, 0xc8, 0xd8, 0x04, 0x56, 0x36, @@ -1085,10 +1099,10 @@ mod tests { // Encode to bech32m let encoded = address.to_bech32m_string(Network::Testnet); - // Verify exact match against DIP-0018 P2SH test vector + // Verify exact encoding assert_eq!( - encoded, "tevo1sppl5xpu70aka8nacc4kj2htflydspzkxcdx0hkz", - "Encoded address must match DIP-0018 P2SH testnet" + encoded, "tdash1sppl5xpu70aka8nacc4kj2htflydspzkxc8jtru5", + "P2SH testnet encoding mismatch" ); // Decode and verify roundtrip @@ -1106,8 +1120,8 @@ mod tests { // Devnet should use testnet HRP let encoded = address.to_bech32m_string(Network::Devnet); assert!( - encoded.starts_with("tevo1"), - "Devnet address should start with 'tevo1', got: {}", + encoded.starts_with("tdash1"), + "Devnet address should start with 'tdash1', got: {}", encoded ); } @@ -1120,8 +1134,8 @@ mod tests { // Regtest should use testnet HRP let encoded = address.to_bech32m_string(Network::Regtest); assert!( - encoded.starts_with("tevo1"), - "Regtest address should start with 'tevo1', got: {}", + encoded.starts_with("tdash1"), + "Regtest address should start with 'tdash1', got: {}", encoded ); } @@ -1163,7 +1177,7 @@ mod tests { fn test_bech32m_invalid_type_byte_fails() { // Manually construct an address with invalid type byte (0x02) // We need to use the bech32 crate directly for this - let hrp = Hrp::parse("evo").unwrap(); + let hrp = Hrp::parse("dash").unwrap(); let invalid_payload: [u8; 21] = [0x02; 21]; // type byte 0x02 is invalid let encoded = bech32::encode::(hrp, &invalid_payload).unwrap(); @@ -1180,8 +1194,8 @@ mod tests { #[test] fn test_bech32m_too_short_fails() { // Construct an address with too few bytes - let hrp = Hrp::parse("evo").unwrap(); - let short_payload: [u8; 10] = [0x00; 10]; // Only 10 bytes instead of 21 + let hrp = Hrp::parse("dash").unwrap(); + let short_payload: [u8; 10] = [0xb0; 10]; // Only 10 bytes instead of 21 let encoded = bech32::encode::(hrp, &short_payload).unwrap(); let result = PlatformAddress::from_bech32m_string(&encoded); @@ -1248,9 +1262,55 @@ mod tests { #[test] fn test_hrp_for_network() { - assert_eq!(PlatformAddress::hrp_for_network(Network::Dash), "evo"); - assert_eq!(PlatformAddress::hrp_for_network(Network::Testnet), "tevo"); - assert_eq!(PlatformAddress::hrp_for_network(Network::Devnet), "tevo"); - assert_eq!(PlatformAddress::hrp_for_network(Network::Regtest), "tevo"); + assert_eq!(PlatformAddress::hrp_for_network(Network::Dash), "dash"); + assert_eq!(PlatformAddress::hrp_for_network(Network::Testnet), "tdash"); + assert_eq!(PlatformAddress::hrp_for_network(Network::Devnet), "tdash"); + assert_eq!(PlatformAddress::hrp_for_network(Network::Regtest), "tdash"); + } + + #[test] + fn test_storage_bytes_format() { + // Verify that to_bytes() (using bincode) produces expected format: + // [variant_index (1 byte)] + [hash (20 bytes)] + // P2pkh = variant 0, P2sh = variant 1 + let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); + let p2sh = PlatformAddress::P2sh([0xCD; 20]); + + let p2pkh_bytes = p2pkh.to_bytes(); + let p2sh_bytes = p2sh.to_bytes(); + + // Verify format: 21 bytes total, first byte is variant index + assert_eq!(p2pkh_bytes.len(), 21); + assert_eq!(p2sh_bytes.len(), 21); + assert_eq!(p2pkh_bytes[0], 0x00, "P2pkh variant index must be 0x00"); + assert_eq!(p2sh_bytes[0], 0x01, "P2sh variant index must be 0x01"); + + // Verify roundtrip through from_bytes + let p2pkh_decoded = PlatformAddress::from_bytes(&p2pkh_bytes).unwrap(); + let p2sh_decoded = PlatformAddress::from_bytes(&p2sh_bytes).unwrap(); + assert_eq!(p2pkh_decoded, p2pkh); + assert_eq!(p2sh_decoded, p2sh); + } + + #[test] + fn test_bech32m_uses_different_type_bytes_than_storage() { + // Verify that bech32m encoding uses type bytes (0xb0/0x80) + // while storage (bincode) uses variant indices (0x00/0x01) + let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); + let p2sh = PlatformAddress::P2sh([0xCD; 20]); + + // Storage bytes (bincode) use variant indices 0x00/0x01 + assert_eq!(p2pkh.to_bytes()[0], 0x00); + assert_eq!(p2sh.to_bytes()[0], 0x01); + + // Bech32m encoding uses 0xb0/0x80 (verified by successful roundtrip) + let p2pkh_encoded = p2pkh.to_bech32m_string(Network::Dash); + let p2sh_encoded = p2sh.to_bech32m_string(Network::Dash); + + let (p2pkh_decoded, _) = PlatformAddress::from_bech32m_string(&p2pkh_encoded).unwrap(); + let (p2sh_decoded, _) = PlatformAddress::from_bech32m_string(&p2sh_encoded).unwrap(); + + assert_eq!(p2pkh_decoded, p2pkh); + assert_eq!(p2sh_decoded, p2sh); } } From 3710c31232f65c287e5ee57a4c86759eb2fbc87e Mon Sep 17 00:00:00 2001 From: QuantumExplorer Date: Thu, 5 Feb 2026 11:40:15 -0800 Subject: [PATCH 6/8] chore: update all package versions to 3.0.1-hotfix.4 (#3060) --- Cargo.lock | 76 +++++++++---------- Cargo.toml | 2 +- package.json | 2 +- packages/bench-suite/package.json | 2 +- packages/dapi-grpc/package.json | 2 +- packages/dapi/package.json | 2 +- packages/dash-spv/package.json | 2 +- packages/dashmate/package.json | 2 +- packages/dashpay-contract/package.json | 2 +- packages/dpns-contract/package.json | 2 +- packages/feature-flags-contract/package.json | 2 +- packages/js-dapi-client/package.json | 2 +- packages/js-dash-sdk/package.json | 2 +- packages/js-evo-sdk/package.json | 2 +- packages/js-grpc-common/package.json | 2 +- packages/keyword-search-contract/package.json | 2 +- .../package.json | 2 +- packages/platform-test-suite/package.json | 2 +- packages/token-history-contract/package.json | 2 +- packages/wallet-lib/package.json | 2 +- packages/wallet-utils-contract/package.json | 2 +- packages/wasm-dpp/package.json | 2 +- packages/wasm-dpp2/package.json | 2 +- packages/wasm-drive-verify/package.json | 2 +- packages/wasm-sdk/package.json | 2 +- packages/withdrawals-contract/package.json | 2 +- 26 files changed, 63 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52417adf7c5..8419982791f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,7 +856,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "check-features" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "toml 0.8.23", ] @@ -1290,7 +1290,7 @@ dependencies = [ [[package]] name = "dapi-grpc" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "dash-platform-macros", "futures-core", @@ -1378,7 +1378,7 @@ dependencies = [ [[package]] name = "dash-context-provider" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "dpp", "drive", @@ -1400,7 +1400,7 @@ dependencies = [ [[package]] name = "dash-platform-balance-checker" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "anyhow", "clap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "dash-platform-macros" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "heck 0.5.0", "quote", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "dash-sdk" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "arc-swap", "assert_matches", @@ -1594,7 +1594,7 @@ dependencies = [ [[package]] name = "dashpay-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "platform-value", "platform-version", @@ -1604,7 +1604,7 @@ dependencies = [ [[package]] name = "data-contracts" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "dashpay-contract", "dpns-contract", @@ -1753,7 +1753,7 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dpns-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "platform-value", "platform-version", @@ -1763,7 +1763,7 @@ dependencies = [ [[package]] name = "dpp" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "anyhow", "assert_matches", @@ -1820,7 +1820,7 @@ dependencies = [ [[package]] name = "drive" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "arc-swap", "assert_matches", @@ -1861,7 +1861,7 @@ dependencies = [ [[package]] name = "drive-abci" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "arc-swap", "assert_matches", @@ -1916,7 +1916,7 @@ dependencies = [ [[package]] name = "drive-proof-verifier" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "bincode 2.0.0-rc.3", "dapi-grpc", @@ -2169,7 +2169,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "feature-flags-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "platform-value", "platform-version", @@ -3342,7 +3342,7 @@ dependencies = [ [[package]] name = "json-schema-compatibility-validator" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "assert_matches", "json-patch", @@ -3461,7 +3461,7 @@ dependencies = [ [[package]] name = "keyword-search-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "base58", "platform-value", @@ -3608,7 +3608,7 @@ dependencies = [ [[package]] name = "masternode-reward-shares-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "platform-value", "platform-version", @@ -4276,7 +4276,7 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform-serialization" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "bincode 2.0.0-rc.3", "platform-version", @@ -4284,7 +4284,7 @@ dependencies = [ [[package]] name = "platform-serialization-derive" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "proc-macro2", "quote", @@ -4294,7 +4294,7 @@ dependencies = [ [[package]] name = "platform-value" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -4313,7 +4313,7 @@ dependencies = [ [[package]] name = "platform-value-convertible" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "quote", "syn 2.0.111", @@ -4321,7 +4321,7 @@ dependencies = [ [[package]] name = "platform-version" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "bincode 2.0.0-rc.3", "grovedb-version", @@ -4332,7 +4332,7 @@ dependencies = [ [[package]] name = "platform-versioning" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "proc-macro2", "quote", @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "platform-wallet" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "dashcore", "dpp", @@ -5106,7 +5106,7 @@ dependencies = [ [[package]] name = "rs-dapi" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "async-trait", "axum 0.8.8", @@ -5155,7 +5155,7 @@ dependencies = [ [[package]] name = "rs-dapi-client" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "backon", "chrono", @@ -5180,7 +5180,7 @@ dependencies = [ [[package]] name = "rs-dash-event-bus" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "metrics", "tokio", @@ -5189,7 +5189,7 @@ dependencies = [ [[package]] name = "rs-sdk-ffi" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "bincode 2.0.0-rc.3", "bs58", @@ -5218,7 +5218,7 @@ dependencies = [ [[package]] name = "rs-sdk-trusted-context-provider" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "arc-swap", "dash-context-provider", @@ -5884,7 +5884,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-signer" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "strategy-tests" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "bincode 2.0.0-rc.3", "dpp", @@ -6374,7 +6374,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "token-history-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "platform-value", "platform-version", @@ -7117,7 +7117,7 @@ dependencies = [ [[package]] name = "wallet-utils-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "platform-value", "platform-version", @@ -7249,7 +7249,7 @@ dependencies = [ [[package]] name = "wasm-dpp" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "anyhow", "async-trait", @@ -7273,7 +7273,7 @@ dependencies = [ [[package]] name = "wasm-dpp2" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "anyhow", "bincode 2.0.0-rc.3", @@ -7290,7 +7290,7 @@ dependencies = [ [[package]] name = "wasm-drive-verify" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -7323,7 +7323,7 @@ dependencies = [ [[package]] name = "wasm-sdk" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "base64 0.22.1", "bip39", @@ -7783,7 +7783,7 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "withdrawals-contract" -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" dependencies = [ "num_enum 0.5.11", "platform-value", diff --git a/Cargo.toml b/Cargo.toml index 62e49f87411..2aa148057d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,5 +45,5 @@ members = [ [workspace.package] -version = "3.0.1-hotfix.3" +version = "3.0.1-hotfix.4" rust-version = "1.92" diff --git a/package.json b/package.json index cff16d6fc0c..c1610babb41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/platform", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "private": true, "scripts": { "setup": "yarn install && yarn run build && yarn run configure", diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index 4e2cfbcb667..be43b4a2548 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/bench-suite", "private": true, - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Dash Platform benchmark tool", "scripts": { "bench": "node ./bin/bench.js", diff --git a/packages/dapi-grpc/package.json b/packages/dapi-grpc/package.json index 7d108a53287..f0a90358918 100644 --- a/packages/dapi-grpc/package.json +++ b/packages/dapi-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-grpc", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "DAPI GRPC definition file and generated clients", "browser": "browser.js", "main": "node.js", diff --git a/packages/dapi/package.json b/packages/dapi/package.json index 4fd13dd371b..05b462fcd41 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/dapi", "private": true, - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "A decentralized API for the Dash network", "scripts": { "api": "node scripts/api.js", diff --git a/packages/dash-spv/package.json b/packages/dash-spv/package.json index 361b1ce26fb..b1d717ff377 100644 --- a/packages/dash-spv/package.json +++ b/packages/dash-spv/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dash-spv", - "version": "4.0.1-hotfix.3", + "version": "4.0.1-hotfix.4", "description": "Repository containing SPV functions used by @dashevo", "main": "index.js", "scripts": { diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index 05d48c61d71..ffa247a08d5 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -1,6 +1,6 @@ { "name": "dashmate", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Distribution package for Dash node installation", "scripts": { "lint": "eslint .", diff --git a/packages/dashpay-contract/package.json b/packages/dashpay-contract/package.json index d3a86b99237..83de8749b68 100644 --- a/packages/dashpay-contract/package.json +++ b/packages/dashpay-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dashpay-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Reference contract of the DashPay DPA on Dash Evolution", "scripts": { "lint": "eslint .", diff --git a/packages/dpns-contract/package.json b/packages/dpns-contract/package.json index 9cfbb1a340e..b13c58494bd 100644 --- a/packages/dpns-contract/package.json +++ b/packages/dpns-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpns-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "A contract and helper scripts for DPNS DApp", "scripts": { "lint": "eslint .", diff --git a/packages/feature-flags-contract/package.json b/packages/feature-flags-contract/package.json index 0e8af066988..efd8ec68288 100644 --- a/packages/feature-flags-contract/package.json +++ b/packages/feature-flags-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/feature-flags-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Data Contract to store Dash Platform feature flags", "scripts": { "build": "", diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index efd5808a9fe..89beed53932 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-client", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Client library used to access Dash DAPI endpoints", "main": "lib/index.js", "contributors": [ diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index 8470d6f749c..a4ca9d3478e 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -1,6 +1,6 @@ { "name": "dash", - "version": "6.0.1-hotfix.3", + "version": "6.0.1-hotfix.4", "description": "Dash library for JavaScript/TypeScript ecosystem (Wallet, DAPI, Primitives, BLS, ...)", "main": "build/index.js", "unpkg": "dist/dash.min.js", diff --git a/packages/js-evo-sdk/package.json b/packages/js-evo-sdk/package.json index dacf6588e22..37373e253db 100644 --- a/packages/js-evo-sdk/package.json +++ b/packages/js-evo-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/evo-sdk", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "type": "module", "main": "./dist/evo-sdk.module.js", "types": "./dist/sdk.d.ts", diff --git a/packages/js-grpc-common/package.json b/packages/js-grpc-common/package.json index 800216df697..d0c6753603f 100644 --- a/packages/js-grpc-common/package.json +++ b/packages/js-grpc-common/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/grpc-common", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Common GRPC library", "main": "index.js", "scripts": { diff --git a/packages/keyword-search-contract/package.json b/packages/keyword-search-contract/package.json index 04dcfd33503..9c3a5dfffd4 100644 --- a/packages/keyword-search-contract/package.json +++ b/packages/keyword-search-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/keyword-search-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "A contract that allows searching for contracts", "scripts": { "lint": "eslint .", diff --git a/packages/masternode-reward-shares-contract/package.json b/packages/masternode-reward-shares-contract/package.json index a23a910a931..9c33bfcbe81 100644 --- a/packages/masternode-reward-shares-contract/package.json +++ b/packages/masternode-reward-shares-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/masternode-reward-shares-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "A contract and helper scripts for reward sharing", "scripts": { "lint": "eslint .", diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index 7296322e954..8d9bd82b49c 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/platform-test-suite", "private": true, - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Dash Network end-to-end tests", "scripts": { "test": "yarn exec bin/test.sh", diff --git a/packages/token-history-contract/package.json b/packages/token-history-contract/package.json index 18fa946d929..4367c916c33 100644 --- a/packages/token-history-contract/package.json +++ b/packages/token-history-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/token-history-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "The token history contract", "scripts": { "lint": "eslint .", diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index 774dae9a36b..fc1f1ecee06 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-lib", - "version": "10.0.1-hotfix.3", + "version": "10.0.1-hotfix.4", "description": "Light wallet library for Dash", "main": "src/index.js", "unpkg": "dist/wallet-lib.min.js", diff --git a/packages/wallet-utils-contract/package.json b/packages/wallet-utils-contract/package.json index 4fe60109e55..82339eb1107 100644 --- a/packages/wallet-utils-contract/package.json +++ b/packages/wallet-utils-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-utils-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "A contract and helper scripts for Wallet DApp", "scripts": { "lint": "eslint .", diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index eb0f4c0e0e7..72a3d336897 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "The JavaScript implementation of the Dash Platform Protocol", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/wasm-dpp2/package.json b/packages/wasm-dpp2/package.json index fd3304e10d8..2552ec2cc19 100644 --- a/packages/wasm-dpp2/package.json +++ b/packages/wasm-dpp2/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp2", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "type": "module", "main": "./dist/dpp.js", "types": "./dist/dpp.d.ts", diff --git a/packages/wasm-drive-verify/package.json b/packages/wasm-drive-verify/package.json index 7d59e150346..4daf942b651 100644 --- a/packages/wasm-drive-verify/package.json +++ b/packages/wasm-drive-verify/package.json @@ -3,7 +3,7 @@ "collaborators": [ "Dash Core Group " ], - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "license": "MIT", "description": "WASM bindings for Drive verify functions", "repository": { diff --git a/packages/wasm-sdk/package.json b/packages/wasm-sdk/package.json index 6a01b753842..8167744611d 100644 --- a/packages/wasm-sdk/package.json +++ b/packages/wasm-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-sdk", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "type": "module", "main": "./dist/sdk.js", "types": "./dist/sdk.d.ts", diff --git a/packages/withdrawals-contract/package.json b/packages/withdrawals-contract/package.json index 5f894140955..3be6a1fded8 100644 --- a/packages/withdrawals-contract/package.json +++ b/packages/withdrawals-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/withdrawals-contract", - "version": "3.0.1-hotfix.3", + "version": "3.0.1-hotfix.4", "description": "Data Contract to manipulate and track withdrawals", "scripts": { "build": "", From 0a60b4dd10b46ca65a99051f702a274f3604e142 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Fri, 6 Feb 2026 15:12:46 +0700 Subject: [PATCH 7/8] chore(release): update versions and changing to v3.0.1 (#3063) --- CHANGELOG.md | 37 +++++++++ Cargo.lock | 76 +++++++++---------- Cargo.toml | 2 +- package.json | 2 +- packages/bench-suite/package.json | 2 +- packages/dapi-grpc/package.json | 2 +- packages/dapi/package.json | 2 +- packages/dash-spv/package.json | 2 +- packages/dashmate/package.json | 2 +- packages/dashpay-contract/package.json | 2 +- packages/dpns-contract/package.json | 2 +- packages/feature-flags-contract/package.json | 2 +- packages/js-dapi-client/package.json | 2 +- packages/js-dash-sdk/package.json | 2 +- packages/js-evo-sdk/package.json | 2 +- packages/js-grpc-common/package.json | 2 +- packages/keyword-search-contract/package.json | 2 +- .../package.json | 2 +- packages/platform-test-suite/package.json | 2 +- packages/token-history-contract/package.json | 2 +- packages/wallet-lib/package.json | 2 +- packages/wallet-utils-contract/package.json | 2 +- packages/wasm-dpp/package.json | 2 +- packages/wasm-dpp2/package.json | 2 +- packages/wasm-drive-verify/package.json | 2 +- packages/wasm-sdk/package.json | 2 +- packages/withdrawals-contract/package.json | 2 +- 27 files changed, 100 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acac59b0ee8..8565b590202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +### [3.0.1](///compare/v3.0.1-hotfix.4...v3.0.1) (2026-02-06) + +### [3.0.1-hotfix.4](///compare/v3.0.1-hotfix.3...v3.0.1-hotfix.4) (2026-02-05) + + +### ⚠ BREAKING CHANGES + +* **platform:** update PlatformAddress encoding and HRP constants (#3059) +* **platform:** 3.0 audit report fixes (#3053) + +### Features + +* **platform:** update PlatformAddress encoding and HRP constants ([#3059](undefined/undefined/undefined/issues/3059)) + + +### Bug Fixes + +* **platform:** 3.0 audit report fixes ([#3053](undefined/undefined/undefined/issues/3053)) + + +### Miscellaneous Chores + +* update all package versions to 3.0.1-hotfix.4 ([#3060](undefined/undefined/undefined/issues/3060)) + +### [3.0.1-hotfix.3](///compare/v3.0.0...v3.0.1-hotfix.3) (2026-02-05) + + +### Bug Fixes + +* **dashmate:** letsencrypt renewal and dashmate doctor fixes ([#3018](undefined/undefined/undefined/issues/3018)) + + +### Miscellaneous Chores + +* **dashmate:** upgrade to Core 23 ([#3054](undefined/undefined/undefined/issues/3054)) +* **release:** update changelog and bump version to 3.0.1-hotfix.3 ([#3055](undefined/undefined/undefined/issues/3055)) + ### [3.0.1-hotfix.3](https://github.com/dashpay/platform/compare/v3.0.0...v3.0.1-hotfix.3) (2026-02-05) diff --git a/Cargo.lock b/Cargo.lock index 8419982791f..e69a9cc4a9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,7 +856,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "check-features" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "toml 0.8.23", ] @@ -1290,7 +1290,7 @@ dependencies = [ [[package]] name = "dapi-grpc" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "dash-platform-macros", "futures-core", @@ -1378,7 +1378,7 @@ dependencies = [ [[package]] name = "dash-context-provider" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "dpp", "drive", @@ -1400,7 +1400,7 @@ dependencies = [ [[package]] name = "dash-platform-balance-checker" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "anyhow", "clap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "dash-platform-macros" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "heck 0.5.0", "quote", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "dash-sdk" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "arc-swap", "assert_matches", @@ -1594,7 +1594,7 @@ dependencies = [ [[package]] name = "dashpay-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "platform-value", "platform-version", @@ -1604,7 +1604,7 @@ dependencies = [ [[package]] name = "data-contracts" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "dashpay-contract", "dpns-contract", @@ -1753,7 +1753,7 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "dpns-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "platform-value", "platform-version", @@ -1763,7 +1763,7 @@ dependencies = [ [[package]] name = "dpp" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "anyhow", "assert_matches", @@ -1820,7 +1820,7 @@ dependencies = [ [[package]] name = "drive" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "arc-swap", "assert_matches", @@ -1861,7 +1861,7 @@ dependencies = [ [[package]] name = "drive-abci" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "arc-swap", "assert_matches", @@ -1916,7 +1916,7 @@ dependencies = [ [[package]] name = "drive-proof-verifier" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "bincode 2.0.0-rc.3", "dapi-grpc", @@ -2169,7 +2169,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "feature-flags-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "platform-value", "platform-version", @@ -3342,7 +3342,7 @@ dependencies = [ [[package]] name = "json-schema-compatibility-validator" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "assert_matches", "json-patch", @@ -3461,7 +3461,7 @@ dependencies = [ [[package]] name = "keyword-search-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "base58", "platform-value", @@ -3608,7 +3608,7 @@ dependencies = [ [[package]] name = "masternode-reward-shares-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "platform-value", "platform-version", @@ -4276,7 +4276,7 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform-serialization" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "bincode 2.0.0-rc.3", "platform-version", @@ -4284,7 +4284,7 @@ dependencies = [ [[package]] name = "platform-serialization-derive" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "proc-macro2", "quote", @@ -4294,7 +4294,7 @@ dependencies = [ [[package]] name = "platform-value" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -4313,7 +4313,7 @@ dependencies = [ [[package]] name = "platform-value-convertible" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "quote", "syn 2.0.111", @@ -4321,7 +4321,7 @@ dependencies = [ [[package]] name = "platform-version" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "bincode 2.0.0-rc.3", "grovedb-version", @@ -4332,7 +4332,7 @@ dependencies = [ [[package]] name = "platform-versioning" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "proc-macro2", "quote", @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "platform-wallet" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "dashcore", "dpp", @@ -5106,7 +5106,7 @@ dependencies = [ [[package]] name = "rs-dapi" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "async-trait", "axum 0.8.8", @@ -5155,7 +5155,7 @@ dependencies = [ [[package]] name = "rs-dapi-client" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "backon", "chrono", @@ -5180,7 +5180,7 @@ dependencies = [ [[package]] name = "rs-dash-event-bus" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "metrics", "tokio", @@ -5189,7 +5189,7 @@ dependencies = [ [[package]] name = "rs-sdk-ffi" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "bincode 2.0.0-rc.3", "bs58", @@ -5218,7 +5218,7 @@ dependencies = [ [[package]] name = "rs-sdk-trusted-context-provider" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "arc-swap", "dash-context-provider", @@ -5884,7 +5884,7 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple-signer" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "strategy-tests" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "bincode 2.0.0-rc.3", "dpp", @@ -6374,7 +6374,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "token-history-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "platform-value", "platform-version", @@ -7117,7 +7117,7 @@ dependencies = [ [[package]] name = "wallet-utils-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "platform-value", "platform-version", @@ -7249,7 +7249,7 @@ dependencies = [ [[package]] name = "wasm-dpp" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "anyhow", "async-trait", @@ -7273,7 +7273,7 @@ dependencies = [ [[package]] name = "wasm-dpp2" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "anyhow", "bincode 2.0.0-rc.3", @@ -7290,7 +7290,7 @@ dependencies = [ [[package]] name = "wasm-drive-verify" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "base64 0.22.1", "bincode 2.0.0-rc.3", @@ -7323,7 +7323,7 @@ dependencies = [ [[package]] name = "wasm-sdk" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "base64 0.22.1", "bip39", @@ -7783,7 +7783,7 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "withdrawals-contract" -version = "3.0.1-hotfix.4" +version = "3.0.1" dependencies = [ "num_enum 0.5.11", "platform-value", diff --git a/Cargo.toml b/Cargo.toml index 2aa148057d3..6a94c7b4fa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,5 +45,5 @@ members = [ [workspace.package] -version = "3.0.1-hotfix.4" +version = "3.0.1" rust-version = "1.92" diff --git a/package.json b/package.json index c1610babb41..bc3781a9e8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/platform", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "private": true, "scripts": { "setup": "yarn install && yarn run build && yarn run configure", diff --git a/packages/bench-suite/package.json b/packages/bench-suite/package.json index be43b4a2548..e035c4da5e9 100644 --- a/packages/bench-suite/package.json +++ b/packages/bench-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/bench-suite", "private": true, - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Dash Platform benchmark tool", "scripts": { "bench": "node ./bin/bench.js", diff --git a/packages/dapi-grpc/package.json b/packages/dapi-grpc/package.json index f0a90358918..ef262596a87 100644 --- a/packages/dapi-grpc/package.json +++ b/packages/dapi-grpc/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-grpc", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "DAPI GRPC definition file and generated clients", "browser": "browser.js", "main": "node.js", diff --git a/packages/dapi/package.json b/packages/dapi/package.json index 05b462fcd41..8d568971c9d 100644 --- a/packages/dapi/package.json +++ b/packages/dapi/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/dapi", "private": true, - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "A decentralized API for the Dash network", "scripts": { "api": "node scripts/api.js", diff --git a/packages/dash-spv/package.json b/packages/dash-spv/package.json index b1d717ff377..df6a685f3ac 100644 --- a/packages/dash-spv/package.json +++ b/packages/dash-spv/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dash-spv", - "version": "4.0.1-hotfix.4", + "version": "4.0.1", "description": "Repository containing SPV functions used by @dashevo", "main": "index.js", "scripts": { diff --git a/packages/dashmate/package.json b/packages/dashmate/package.json index ffa247a08d5..c123be0a4d2 100644 --- a/packages/dashmate/package.json +++ b/packages/dashmate/package.json @@ -1,6 +1,6 @@ { "name": "dashmate", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Distribution package for Dash node installation", "scripts": { "lint": "eslint .", diff --git a/packages/dashpay-contract/package.json b/packages/dashpay-contract/package.json index 83de8749b68..4e40baed724 100644 --- a/packages/dashpay-contract/package.json +++ b/packages/dashpay-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dashpay-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Reference contract of the DashPay DPA on Dash Evolution", "scripts": { "lint": "eslint .", diff --git a/packages/dpns-contract/package.json b/packages/dpns-contract/package.json index b13c58494bd..dc83e624bf5 100644 --- a/packages/dpns-contract/package.json +++ b/packages/dpns-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dpns-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "A contract and helper scripts for DPNS DApp", "scripts": { "lint": "eslint .", diff --git a/packages/feature-flags-contract/package.json b/packages/feature-flags-contract/package.json index efd8ec68288..d09d1ec55b7 100644 --- a/packages/feature-flags-contract/package.json +++ b/packages/feature-flags-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/feature-flags-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Data Contract to store Dash Platform feature flags", "scripts": { "build": "", diff --git a/packages/js-dapi-client/package.json b/packages/js-dapi-client/package.json index 89beed53932..9faf3fdf8de 100644 --- a/packages/js-dapi-client/package.json +++ b/packages/js-dapi-client/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/dapi-client", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Client library used to access Dash DAPI endpoints", "main": "lib/index.js", "contributors": [ diff --git a/packages/js-dash-sdk/package.json b/packages/js-dash-sdk/package.json index a4ca9d3478e..476540167b2 100644 --- a/packages/js-dash-sdk/package.json +++ b/packages/js-dash-sdk/package.json @@ -1,6 +1,6 @@ { "name": "dash", - "version": "6.0.1-hotfix.4", + "version": "6.0.1", "description": "Dash library for JavaScript/TypeScript ecosystem (Wallet, DAPI, Primitives, BLS, ...)", "main": "build/index.js", "unpkg": "dist/dash.min.js", diff --git a/packages/js-evo-sdk/package.json b/packages/js-evo-sdk/package.json index 37373e253db..4f4be34f722 100644 --- a/packages/js-evo-sdk/package.json +++ b/packages/js-evo-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/evo-sdk", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "type": "module", "main": "./dist/evo-sdk.module.js", "types": "./dist/sdk.d.ts", diff --git a/packages/js-grpc-common/package.json b/packages/js-grpc-common/package.json index d0c6753603f..fe6046b2769 100644 --- a/packages/js-grpc-common/package.json +++ b/packages/js-grpc-common/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/grpc-common", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Common GRPC library", "main": "index.js", "scripts": { diff --git a/packages/keyword-search-contract/package.json b/packages/keyword-search-contract/package.json index 9c3a5dfffd4..e38d95809fb 100644 --- a/packages/keyword-search-contract/package.json +++ b/packages/keyword-search-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/keyword-search-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "A contract that allows searching for contracts", "scripts": { "lint": "eslint .", diff --git a/packages/masternode-reward-shares-contract/package.json b/packages/masternode-reward-shares-contract/package.json index 9c33bfcbe81..f132687af15 100644 --- a/packages/masternode-reward-shares-contract/package.json +++ b/packages/masternode-reward-shares-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/masternode-reward-shares-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "A contract and helper scripts for reward sharing", "scripts": { "lint": "eslint .", diff --git a/packages/platform-test-suite/package.json b/packages/platform-test-suite/package.json index 8d9bd82b49c..c9d4be0f99c 100644 --- a/packages/platform-test-suite/package.json +++ b/packages/platform-test-suite/package.json @@ -1,7 +1,7 @@ { "name": "@dashevo/platform-test-suite", "private": true, - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Dash Network end-to-end tests", "scripts": { "test": "yarn exec bin/test.sh", diff --git a/packages/token-history-contract/package.json b/packages/token-history-contract/package.json index 4367c916c33..2df60d367e3 100644 --- a/packages/token-history-contract/package.json +++ b/packages/token-history-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/token-history-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "The token history contract", "scripts": { "lint": "eslint .", diff --git a/packages/wallet-lib/package.json b/packages/wallet-lib/package.json index fc1f1ecee06..739555b61cc 100644 --- a/packages/wallet-lib/package.json +++ b/packages/wallet-lib/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-lib", - "version": "10.0.1-hotfix.4", + "version": "10.0.1", "description": "Light wallet library for Dash", "main": "src/index.js", "unpkg": "dist/wallet-lib.min.js", diff --git a/packages/wallet-utils-contract/package.json b/packages/wallet-utils-contract/package.json index 82339eb1107..e521e0b6518 100644 --- a/packages/wallet-utils-contract/package.json +++ b/packages/wallet-utils-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wallet-utils-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "A contract and helper scripts for Wallet DApp", "scripts": { "lint": "eslint .", diff --git a/packages/wasm-dpp/package.json b/packages/wasm-dpp/package.json index 72a3d336897..e1a8f78b9e5 100644 --- a/packages/wasm-dpp/package.json +++ b/packages/wasm-dpp/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "The JavaScript implementation of the Dash Platform Protocol", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/wasm-dpp2/package.json b/packages/wasm-dpp2/package.json index 2552ec2cc19..3023f914378 100644 --- a/packages/wasm-dpp2/package.json +++ b/packages/wasm-dpp2/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-dpp2", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "type": "module", "main": "./dist/dpp.js", "types": "./dist/dpp.d.ts", diff --git a/packages/wasm-drive-verify/package.json b/packages/wasm-drive-verify/package.json index 4daf942b651..d284052e9c9 100644 --- a/packages/wasm-drive-verify/package.json +++ b/packages/wasm-drive-verify/package.json @@ -3,7 +3,7 @@ "collaborators": [ "Dash Core Group " ], - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "license": "MIT", "description": "WASM bindings for Drive verify functions", "repository": { diff --git a/packages/wasm-sdk/package.json b/packages/wasm-sdk/package.json index 8167744611d..b7209c78506 100644 --- a/packages/wasm-sdk/package.json +++ b/packages/wasm-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/wasm-sdk", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "type": "module", "main": "./dist/sdk.js", "types": "./dist/sdk.d.ts", diff --git a/packages/withdrawals-contract/package.json b/packages/withdrawals-contract/package.json index 3be6a1fded8..fc3401aacaa 100644 --- a/packages/withdrawals-contract/package.json +++ b/packages/withdrawals-contract/package.json @@ -1,6 +1,6 @@ { "name": "@dashevo/withdrawals-contract", - "version": "3.0.1-hotfix.4", + "version": "3.0.1", "description": "Data Contract to manipulate and track withdrawals", "scripts": { "build": "", From 36a6359b4498a8a4d398f857af7fc400341a82fe Mon Sep 17 00:00:00 2001 From: lklimek <842586+lklimek@users.noreply.github.com> Date: Mon, 9 Feb 2026 01:25:58 +0100 Subject: [PATCH 8/8] test: regenerate test vectors for v3.0.1 (#3065) --- ...9cb9450b88b827499093467ded896aeec84cc.json | Bin 30187 -> 0 bytes ...e9a7bb2f706e5b458cde86f4eb66de7d8cc6f.json | Bin 0 -> 30572 bytes ...662e038df51e859dea21df6086d8087bdac74.json | 1 - ...b7f3743e11f8d61eb4ee5e5720bfa867171fc.json | 1 + ...842ebb7bc074176a28d1e7c06e322748ee600.json | Bin 31372 -> 0 bytes ...8cbe3a47c21e93ea2e5326d36fa8581ed5bb9.json | Bin 0 -> 31435 bytes ...662e038df51e859dea21df6086d8087bdac74.json | 1 - ...8f5cac1f47552c7ac98cd8d1ed4d9552cbfd6.json | 1 + ...daa7f11dfff63969ad07d74a36a45514c3d33.json | Bin 26703 -> 26700 bytes ...662e038df51e859dea21df6086d8087bdac74.json | 1 - ...8f5cac1f47552c7ac98cd8d1ed4d9552cbfd6.json | 1 + 11 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 packages/rs-sdk/tests/vectors/test_fetch_address_info/msg_GetAddressInfoRequest_082a22793b0dca0c3a461a625da9cb9450b88b827499093467ded896aeec84cc.json create mode 100644 packages/rs-sdk/tests/vectors/test_fetch_address_info/msg_GetAddressInfoRequest_9daaf029e6ac9943bb15ea90cd7e9a7bb2f706e5b458cde86f4eb66de7d8cc6f.json delete mode 100644 packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json create mode 100644 packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-3f54d536a6c29767dd83acf116cb7f3743e11f8d61eb4ee5e5720bfa867171fc.json delete mode 100644 packages/rs-sdk/tests/vectors/test_fetch_addresses_infos/msg_GetAddressesInfosRequest_0ef99c66df43e7caf742064f48e842ebb7bc074176a28d1e7c06e322748ee600.json create mode 100644 packages/rs-sdk/tests/vectors/test_fetch_addresses_infos/msg_GetAddressesInfosRequest_446d97c31acd0e7b72e15a6d19e8cbe3a47c21e93ea2e5326d36fa8581ed5bb9.json delete mode 100644 packages/rs-sdk/tests/vectors/test_fetch_addresses_infos/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json create mode 100644 packages/rs-sdk/tests/vectors/test_fetch_addresses_infos/quorum_pubkey-106-364421c45be18cc07a7300c9c038f5cac1f47552c7ac98cd8d1ed4d9552cbfd6.json delete mode 100644 packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json create mode 100644 packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-364421c45be18cc07a7300c9c038f5cac1f47552c7ac98cd8d1ed4d9552cbfd6.json diff --git a/packages/rs-sdk/tests/vectors/test_fetch_address_info/msg_GetAddressInfoRequest_082a22793b0dca0c3a461a625da9cb9450b88b827499093467ded896aeec84cc.json b/packages/rs-sdk/tests/vectors/test_fetch_address_info/msg_GetAddressInfoRequest_082a22793b0dca0c3a461a625da9cb9450b88b827499093467ded896aeec84cc.json deleted file mode 100644 index 284dd382c4b5a6b6e009283ba1e47a579ce62dd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30187 zcmeI5!EPHz5JYpej zu|HV;3_rJYKfUC7_1D9fulGUa?}vZx%ZtzX{(SL#@yo^2)vteCefT)7t{2mCalI;E z>%}zP<#@{9{JhD}`7?2xLF>ilj&X;R=2O>s{k2iMvCC~+=4P?I&5z6S*Hn%=&agjE z!X#Y6T#^c5-|U0Pxz^jo=9b^A8T*oIJ;VI_da+GVCD(e=?WX)Drz{}?M8agDV3ctT zUlKg)w^z=DLo5^FY&ycJJOu8SwbuK*0`%E*0HOj@!7ibb;%0xIhR=q85*G1YFE%$N z$^@F<=Wx{v;4~2}6rh-_-XiBPpbQtSF|Wj3WB*%n~U$ zGX+#LW#v|()+aHgtzxFIL`3^avHS}zrUbDY6IgLng)plOz@9OE`t^4PYO!v2H1Lj$3^f_@f5oCl+2QeKwrOT zbN-IVw?vfD9`!J3*?gV}+Y#s@^c_pv85bzzHws0O-HW~tJMHWU#RE8CKc0+ENcO+z>M zq^l~!iJ}bGjXM>U+^vntLuJhp@>&T)i)anDZ562yYdCmlO zF?j4!(db+OOX6~$fWkwE?65WW{w7FZVMHZvMQF!|?$BpSk1Vo^X#~)hRgBm{Tgo;hV{x=sGrmD;LSjnjHe9z=OkgY__X)PN+25L7G+Edy_ZX%JN;IijcM8}H zHilEPR-VI@xPjUyWY?_>CpM_gAD~jL1}L(y*I5}qAlmLd2_c#D+wn@P;BPu9Cr|vw zY7}3VUmPZGX&itCVN^%Hk%Z~jOfil@78X*)+JIf8mMy9#1t1z|bMlP?mM~mfEwwC^ z=|va=TPAr4hNAMNa?cXRT2Z)61bD4TQhF+jY6#w7Re`6?xlSjmsTqi=ai*Ap&3NdO zQ|$QuT~LjO@2ZnVPG%$I6h82VVCj$jDNv-cDB2X&Us6P-w40U;7YIX*Pd_!p5mJiC z)HM}qm&lG+E#b4E=KhA^rJ+|-91RxS~4}@SlvkOMVASK6? zv_+$zCQ*|Jt3*QnzM|`orL$N%a3`ZxI)&-lsE??C27v)YFpInR&kc(+~WG$_R zB|?XE1IkwBt{9<4Y~sn?kPdy}!cM8P`YmPb&Y`SamqiNDh!7$o5@T76kJKfD_7a07 zOjJ0JbrR)N$=B3T1-8FEfoj@~2?=4Cklx21-E28Je8=YqkfXzQe2xG)I@I{c;#HQb zc5ZGnsltAX)Yo)&~mm%)s)j%-I7P`yZee(40r*s>JM^IlsIbr7E+E zsmj!&DoM7%Dhqod7PUyh;9cfI36z4ZL8(nKxiBU4DXjb@K(JDYAP?if6)2ZQE_{qX zv?bKFlDvh&60%!S7v@%8%fgCE-br8;8od5E_DxsRokyksL`F-npyE$FW;_rL^n_?hWf7k<s~U_5jg8<5X2SyuswqnkX((A> zgR0?9qZ;DMSHH-cQz>k-;Za>_z@x03>XBn10nG!?yB1;McFz{MW|gzD((lrZEgq#sYThjjwcADax&0MM>$TO|h;b$0HJ1 zxr{gMmcd!Yx==_|9Z@oZ`Ka@kGFSdXNBF@V@%#Uhfe&n4@u`LRT)_FeLk;~`?Tk-E TJoyhUPP}mNt9FpTK0SW}57tzS diff --git a/packages/rs-sdk/tests/vectors/test_fetch_address_info/msg_GetAddressInfoRequest_9daaf029e6ac9943bb15ea90cd7e9a7bb2f706e5b458cde86f4eb66de7d8cc6f.json b/packages/rs-sdk/tests/vectors/test_fetch_address_info/msg_GetAddressInfoRequest_9daaf029e6ac9943bb15ea90cd7e9a7bb2f706e5b458cde86f4eb66de7d8cc6f.json new file mode 100644 index 0000000000000000000000000000000000000000..f4997673c769a0a5d9cefcc6a58b5419d63bc587 GIT binary patch literal 30572 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 literal 0 HcmV?d00001 diff --git a/packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json b/packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json deleted file mode 100644 index bdc3fb61efc..00000000000 --- a/packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json +++ /dev/null @@ -1 +0,0 @@ -a478196e10c6c4044293ffdae6dc5c2f9f6b2efe9065ae49ae6bdc6ce0e01a7194979219c610b45b3a10e668ce9b8e87 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-3f54d536a6c29767dd83acf116cb7f3743e11f8d61eb4ee5e5720bfa867171fc.json b/packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-3f54d536a6c29767dd83acf116cb7f3743e11f8d61eb4ee5e5720bfa867171fc.json new file mode 100644 index 00000000000..03c9343bb76 --- /dev/null +++ b/packages/rs-sdk/tests/vectors/test_fetch_address_info/quorum_pubkey-106-3f54d536a6c29767dd83acf116cb7f3743e11f8d61eb4ee5e5720bfa867171fc.json @@ -0,0 +1 @@ +89fd8b32066db8e2822fa8fcb054dbb11f9bcdff3f166d2e05abec5068121d724fba1977319b7ffd1d6d44091c256d97 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_fetch_addresses_infos/msg_GetAddressesInfosRequest_0ef99c66df43e7caf742064f48e842ebb7bc074176a28d1e7c06e322748ee600.json b/packages/rs-sdk/tests/vectors/test_fetch_addresses_infos/msg_GetAddressesInfosRequest_0ef99c66df43e7caf742064f48e842ebb7bc074176a28d1e7c06e322748ee600.json deleted file mode 100644 index 720df657217bcfa63893a574b0973b22ec49f668..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31372 zcmeI5&2Aev6otF?QwUybp^+uW%1iVCf-DNei=seN*ljj}-@WG}8EO10er(-M2T*K{ zL=Nvg=iDEToYD7-)#~!w?cM#aUw^;+xcZ($Uf3L;=jfL&U+!-2?{Dw(;-@lzL8oV{ z)%4+deS9N_o8<_IK;j6?=MyGxK)2mI!frV{p|)BDjk9oWZ{{SKIQ~t*7ZG$5Vdue4pp} z7qlk^?biDn#yy@ipSsKYZ;d*R-5lC7*XzTFJl&LEQ#tB5!|6OR6LT?hgDaSQeKLY` zZFuC#j`;NK*UTI3Pu^n@D0YZetYMPIlIGpKdtv4 z*82`~Di4AC$6C9SUjh28I{;CEsbCk=adCe-53t0hJqE=ro?or^*Tu@bk|*`BEZwmp z)&Z!oh$9^>uu*EG;*BK^Ect@xEm6$Y zIj_XoU}EvAgWTDacR&AGrOl10v9fuc{n_A(RgIxDVxG++1h)kxMAStAsAkGmSXDzT zxv1Z;wu+g8FA?nRsdP-(VN1(48*c#pc zMSix?l-eg9M}Erpn4}6*OfsScTiN4fysP+GA`OqNN@Z5RNQjPfI|f^(3|M7)DzGw3 zV1EME#H<<|`KiJOL}k0_h>&q%g957ts#0dD&DdhNmd>NJwOC+%yFDL{mY^6P@-L@7nNkOD)cN91vCm&4%s}O+3aj*?mCYsdw zBP2II$nxEuW8&H}J-8i1{YKei0X%lSXP63<2CgD`C)z*e7 zvUiOFU1dqAsyq-mC0Y$ocp-ZL#t(?Ldyk1p=KOZN3Tp6Iv5<@r`%S7*d|7_UFmgjm zHa4om-$=sjMwwz#D_&Sg6>9_OBC%}pDySM4fM}r1$&DCG7_P0Bnin$LkF-pISFVDn zlq*fFzdq3c{B1z~eFKR;Y304I-?UtfLMpH{5CdQd!0yg8JPfoGp`*$DJ9)7Ay z8abIp$SFM8#t4asEA_G81*XWLXj4>uNf4RPZdx*2APhA=_0$lDNeLoFM@Pu~vJEa9 zRNE>x+ek-3oQgXfmqF2w5-xYwARTM*)PQ62wy<3GFiyt{u{!R|2(ze{{)aHhyOQx=-c~qewy|)GiHiPZggTYc~U0qlz3q(k#Zr% zV3J8eZC_)1V_Q$-wdsE{*o}`c462-bfH>3W(x@w~7Vgv(xQio-?reL_B@`4TI~Ib} zEP=~e#bAZwh6~Pb8dtb8a&7iYMTIzJ6{{pJ3{aU;XZ5F9#@j7_7+3IyzUWX*vD@A( z5ui+H;CQ4id51)kJaODe7n96*7?)lvZS)V;c|+D;%P-?)gl38=E1X|0v5bGx1J4f^ zJm+XUf4<=J9FzGg8XuWCNh$sN7u6K04-S;s?>5*K^rY5z-OZ(FnpCV>R##!)4e-T` zr@`JIjIuc#X�?8R^d_$XiKk8UOl~$p~q|a~@64pD*}4$7KGB4aTK;n4{-5(eK?n z$g64zje#pOdUvU^{W5r#?Nw~5z=V2reMa%ZZ7k2+5bw}=^(n2<#LW?NbH;M7N{+cv zW9|%DZxYFUAj|zvXE)vKzbPVORvVmL*D`w?&Yz2)3hH`6Pwmm{T3TkIe(kWrHmdVXSia6s?Qls-pT0qq5kJMMfN4o77QV zaM`Q|g-U3p80o&Hi0WVcJSXL|e3ru}H=A4xn|%pbuXXvVH@+t8(f7NR4}`epcKnLB zfB4yuWU=KbLwu(2P%Nfks34C}l0-}%L5>6UIF6XOB3s$)Ag8Dxtn|hA{#3$X*>KaR zs{d6@kN&} Qn)$*Tk6vmA`StnXZx$sE8vpDe=b8-Sb~}YdEwivtP%`iUBQg z$nLJHU%!6!43**6tJUiI%fsW-@1Os?e!u#fLtfa-&vW$Kr%#U$Pfrg|dGSLTz@Wpk z)oQbzUtX=I%MtkGziBhW^6`MstH5nHN61|c516f1LE{{l({lWDIjSUFP16Cm_H1V3 z^-Qg~?aewReX}i(+x0ZP%kh-oJm2Mc z{srxcLAUGOJH{PPT28&q`#%~rkKOE>nY;D=K2JC0*Hn%=RyeE^H*ps?H-v)QcLyi3 z*6p6h?w%KK8U7M&U1a(5cD;{ZCEU91epg<}s>MsNh^N>JOc}@U4gTXod*_Tt__8e? z<>xtfYAS;Gi>=#3XvB4Pez2(6RJ@Dpgt$AbQz6>z)_Ejhx-@80r7E0OR9^jyA6?- z=_g1tZ3EbA82|y^qjZQFlE9e-H&2z(F&mG;1i|9X>s7d%UZ3Mb>SE4FD{si!va!t9 zHzxKRpNPE4#ll)R@+uUIVRa&~-juq04ZZKCy!rZPi)rB!C7k-$n2_&E$o=sal>nvg zDV3~fOZSvoWvTf7Db0$R0$fE=`mHb}h}{{-qFeW3e2aYmE|rvcrN$S?4+R?MgeS1M}HLIxrqW9IlC)4Rvx_iLEl*np2apOC6NBy zRTHY3C~#u}V3Tnlh{|~5xlB56NhKkVW{Sf~@>VTQuCuvQ&S$V`m*iTlAAb~=Jdp_5 za1WHqcDTQYh&m5b-HeZ8vbR=}_d7-`Djo4EEzBq_CXy7BM5f53uzqG4D-14YGYT%O zShm>xVL+B0&`R!LQX-x*^&o5{mq64WDL^R#XLqEUat0fHYrv`y3_h%%5X|~aG0LOj zO%YM53esA4CImvZMK@4ECW@A5;}uw$sB7X>;Fw6@16CwMETe1&n>ee|F0m_tsu|I6 zm0qK|#z<0sbmY*bP0`U0=f69P%>HW2xl zf~}5$q$3Uk)HUg7UYT?-#h9=v-QR0c4$2cN9cujBg&DvFCSktiOkh;SViXu>jQ_Ap z_8q3khG;YE^eSM+CE1``;BWfjh*i-DuWV8XudxF0LJ!N3XtlDEzG+NACwuiHQ&LA3 zu7TDjukR6f8m`oY%+j$~HI9j}#gs{lMTu3c#4*$0TK0`&V}wD4jFys=5XOWLja8+{ z_R(BY?1~Aqe<10WjULth4vRBYwYs&sqU20S#=6DLc?8ZI3T3=vHDplYY@DGmbJnkF zS?*Yn7sG$>O#f2(P084lC2-?a1*)G#2$5-atRF2h+67Qn?y`QGos<_cC>_H$!$Zc$ zibX6_tQ8XoQ5TR!-s==YEli{a?Ubb61-Wu$BkwxlDaVcDyqhzVQcC5bNS!Pc}_ zlAa72swab$UHy&$Z_3uNv6(PUjVaPZe+Wd~eU^tBC~kz|h8sFx{hr&iep51(n1PYO zW_`7ImAn3&s>K;>BfX6O?r-ep{~zjt=M2TkCl`E9!ZN>T@j)E=HM49s)D$IaG;}Er zGzdGz?oXsLvjH$?AMrndtA?JzYS)K}vj6GNHB++3FtNwPz*Zc%%nlaE)E2^GTcc3B znz3t@W-ki1K}n?}do#03v?Oc{=~fTM$0fOGxAee-qDMV1P+nP7CB4bA(X>`~hE6RP z(lM|#u@gm-u1ZO+j}jWOMFtBGof66uc}(~#|H`HQ!PK>)x9q||s&uKOpx=MBIFvKa zT*l8^$CFPk_?(1ge({3O48_SO8XsA_h)?$p#L1xQr^@X&8_X85AC0o5Q~)Yzr7x>U zhni;mSNlEZlJfsDcyV2e!Z%b@d4aTMeLdJNei^?={WpyO@GtmW@OeSt&H12oI8EoW zg?LF#Dp#H0(|mJHzyRoUK9>%Rbf^uU^5imjsp@ZshT~F-*IZn3Zga!SGq*0>ckZ{d z5t_J%V(#Hs?rO;~_jAlWBWr%KIo(aEHS z%2kyfb+%6jY8>hi^6nZ zrg%+4k5!SCM&V>ke*Q)vQjeKpv5Hqlzu}=Vfq1K4Hz4-eqp1)|J9WFENS&#U2xQer zXT>)81gmQ}1}dPCf})@O=~L??P^uXwaT#v}3jbA0PqQ#} z%KCmA3o$G=yqM#g5d_8$D6Q0lSkd7RH2r}tQ`y!35kxX#s0F-f8HT;ApBlH~78x;C zaLjCw*if0k85|R8if@0IkopPRZ4+1H!&?<~ZA>PLa6?C(^qDB&Qm{2jqPLNupJ8lJ zRgHtijouP(7^sROMQTF`q}Ho2%BTT>r0L5*T(0fTIk}Sztm#&zMBq}NQ3FNt8i?5~ z4D4PA#AF;nP%cHF(qhauth!`1gd|GG;E9SoCQ4Goel;dkbyO3^E8q&erqaifD56dj zsg8jzV6duzy9}oH8K9a8)nucGs>9Zd|K!VKe4fMibnz+9CfCCjUmey9K)!>FZ`L~c zR=D!v5SQKNFNXVXpA!XNAU1TTaY{8ofRu^kJ6@@*7seA#|NcHiF@fMuyfQ_FT9t?J zs#xjeNd4%iVr60{kSL?6lN!5~_-aUmAD3((a?hm2H#{*(CuHdoQ=->49kF5vT&<{F z=)f&d*^^0SfVaj<25VqrLWxguXQRmarV-*#RMc*;B0dHy7{a%E{x5&z!1su5>5;VF d#pgwFqK5hdKlq-^lW(7R3$w6ozwWZtpG7(uxI3TiPizop0_oGi7F=1uLzlQ0PKCGsw~cwFnr3rU?Ne zCb$I+ktp)UeVZTraQiW_MuST&8W)U9U5G)0_J=V>LrP*WCPwl7zS~CQuUYQ5ob#UN zyyt#CseLl372hz?KTpSJDS^KiL{Y3UuzHcew=Et9yJC3Qv`|RtxVOy0L|+`|Mr;)3 z8fZ!jTnZ$RT&d%}J&g#J&B6Qiww6N2Vh2-M9dC8oIMZjLKa&ikTcDLVm<;MDHKHfW zP2ABNMgNG2jROM53=4Oa=s4Z!VVw6(^qBZ#NN8y^S6Mg|v`}o}EnNafMm*H_a`ko{ z&-6rbcr&A^wo!}zKN&fm zW8l_q2m3Y(RJ6Dl%i0*7qvOtjC<;Lnr>gzWQbstT@t!JOWgd!fU!OpmjJi zPX=bhGqJvr!3`QXs&nCdix=qlxIw8{Blir&`M3l}_bg=<$`IzcXEf?QqwaX#m4ctk z*jUrx;#$*YNlc!`u(Af;u>_AWv2v@27Z=#7z*Dsb4p#nKpeZAPL%mEZ+skoeDkA%6->j zLu4#e4x4bvS7AtSt%)@~E)ItxD$%~H1g2_L1`-)r+@UQtwq?1TA|G2$$nrME^9G*S zXk*<{E0Au*-DL^v&Ck*t$CY_ewAXuz*5w@*{+wseFcenW=OO~9a|t}YB!=Qn3l;4V z6;-NA;B8k%>DTPBJY_d*W#a3zvb)31X94{nwJnJC#};Q|vJUg7J#H!m=_ zPL|6+VUCz-GzZzMZPeXFN=fRqIVP*`vlf6H=@ z*@act7h7JC_`Q-i%iKEd^if<=f^LS~(DoiubdZU>j!EJiS{oZX-ub4cHcm38xvc~L zS11496UG|aE(WAr__R5!jAnFo3=?agz}%QcpmhA=3Rcci9h725(l(EMYhqWoS?T_CfE*YChpIjNm9~7pWaEc)3}*$T zoU3w@ZjX*4@uI^@=q;=cSOl6TrYK{F4jLrq%-ZYmYi|~ddu;4j=<_DAmaZb*WxCO# zeHv^!frr+!pOw(OK-re>pf8QcdU=Y+9SnlpYt3ZRLKE|qWJ^-LCf`k@Sxc(VhAp)KHQ?Ve&U^%O8Or!a5H^WFZE`=-CyMoI=KdP!LJzm{E7rNaHPsJ z*N6S#bg7hjTw^N__Z@h4#EmcVX==DhW=p46Rz3#@sGf}CkwJ%dNv&lU7OM=jcgJvq z)+zn)J{p#+T5-OKT!i|hq!asN`)~4A?DD@K4pJZHo}zbFwD+m?bv;5ki-XM>o&W}v2 zFAUSHL|s2^msQMOEvurlku6;2Ft}%D9ItXL`*cVGJjLVUdPj}3c?;#shmPCl3KH(2 zH)Z1ry}}eu~KJSN=nM z{N(&EKK%MVQ9u0p7V&<+JKgV2jrSSnr(Wz`zSG}7{+ldrlKwff<9>I%-rbzY__O%L zOP_Yf<1W!sQnw>1NI!`muhh6$#rTS1D5P2YE>6zmjJMtCjb*z`x0*~vDDc&iLma}7>2!2T=izM8)7wH ziote<;$UtYXUfMa2+cZqZ9p{rXof@WZezN2(Kd#qP9A%dZt_B4Cl21m88K1p)HYJq z+%s{o%%cS3Yh%G=(=lz9tK?!{b;~<>;pHuN^UVA%)DCm8QEY0Q*`dyBoYrLGDHIW# zH_|1LW6Ud7LAEfq1hGG)G|+9Dq($3yYNgS_w5!Lgta$*KKF+c+d>2Ap(fN|V+V?EA zBIdi0`RwdNW@p2(9#X}Qs-#K5tKbp>HBLK4fdwufKrwca=d@=9v<1(Rx@`hZaY4fh ziWI$>5Alc?8i4O(+?R^#@T@U}HOsDUy(*KMxsBOUTWErCt5~yWhKEjvl##L#C$<*UmQSUgdysn%Q_JB%Uk9L;LQ8Vp3Jla2bN^CBR$ z(K=9w|5B;)u3oGx)MB+X4$?DG262mZ9ZMUXTeRFW*H(gn#-1w%%r0&bv6UUBtU1N$ zZy4sUm1S?3L4bTH|PHD;MrIc)5f7-z?~HIOD18Q8d29j3;49y$Ik z85ZqkY|ErioJ|9UZD~czlz>q%`BHdOFYY$G8M3w#Z34goZ)z4mEqynOZ1Q2PW5%F5 z=NRf1`)B$g_l;s}J=_Zi1(WOIrIUNp$2jK*GpshxUgH)+qvZ}(V-~G;Z&@Z;d(FiX zN7)y4iIC^DN-nCfOV^D19_6yMBrKM&1hAHo=l)`O?%%GzApiC2vpb}8q^tpHn`Vp+ zq;S5(!2%kW zg#?V0<>tt@T%OJ?8FSmDlAYC8Q!{Mq(^jmqVA9&+WF>?GjG>;RTLU4+(_Y$FE&Ty{ zSVPc4M3jTZL6{Gt3W7%uI(`n$;)2FUTk{fg#Rc1i)|TqDU66 zG;8DX<;V!`-&;^t_%D_^1ix84Iq?=Uh!fj5-aV<#F$*)x5Tr~$$GGHEj+FM&{q@3~ zh@tdFsQH>NiUm^Q@ctT!I6e2uGpDm+PEjBF>Nt1#?2GI4`+U7WjhDvR-|SBPNnhFL z=KV|V$gAR1a(thT4WT}Btb_p0;}mRM{3N$$DYotV;$2}g-96m1J*{F3VurwGY$sqf z4iSh$EbUyndF4>c0*<=S?F2!i%}tOAY;$X8tqs*!t6M%hja#j z>9O>q$R*8K0L?2gu;&tSwuO!t1gmG}qQ)!FN+uo8oO)TG-(H_>yc$|w{j81scDDPr z{k)9Z!)vNZgKtp5hDF`V##%{VVYd~OH!YWH+#2&S(?W5d=|DCRDrZirl<}Y|1x{IF z;O(^m2MX*iD`G&|6ovj1@X`nrUc~{; zs$-+W`h6D!ZaXav^tP4!7aB;NtZ17g<2v;l-fhkBE(G!G)g(6|#9%=zpGiKQa z;uJ&%)^LhIlJY9EXq(sYjFnz{j$xr35`($z25N|Hp;}K!A-G^v;TdO^R71^;ixp0d zv225CKF~PPVvVPAgQ@|WXdAGN^(7pd+Dgpjmt(GTzMms#L7#A>$0nt{-~qA?GcjW@L~ ziN|90R@srwsa253#Vll>u+ce(;dI2Z3BnVYvYck(#_4HJ9G7Rqz#cW!{-4h_dT!nG z>mF+CbC~hy_2()`` zVy@2!$lB#*Y_(M5>|UH*1!R4&ZcW57>jz4ctFAtIzb_7{Ixo?co>J!sh3~-wF?Bnk zFqSJbqqupwCNpa>0G_p7k4!cVb!C@obFgUZ+1EslVX9ZAOD%nuiX}F^rBLJc*+FQB z;U9nK*+cQG&XT+@$m2utDm7GnZhOa(|20wNq)bN&j%O%Vmzun!)In*B6|1CHy$(P#^{@me8(HPY@_eVT$~qw7>duv zQoW`vrrDh>-7Tx}UM$-Te)PauM)-J`F;JOEkEz`hk59m9OzC{Hwa-e;xEpI;f0TYO zI1Y?w0et$-F@2OWBQK~~CdPZqZ0|NRo~v)qUk2un_meTm&tB~#XY mJj>sn^-P){Z93K93^gq)PCmGYRoZ|1!IQt*zR4dyzWobdp3gD> diff --git a/packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json b/packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json deleted file mode 100644 index bdc3fb61efc..00000000000 --- a/packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-1d5eecc0582c07c8837a7f86266662e038df51e859dea21df6086d8087bdac74.json +++ /dev/null @@ -1 +0,0 @@ -a478196e10c6c4044293ffdae6dc5c2f9f6b2efe9065ae49ae6bdc6ce0e01a7194979219c610b45b3a10e668ce9b8e87 \ No newline at end of file diff --git a/packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-364421c45be18cc07a7300c9c038f5cac1f47552c7ac98cd8d1ed4d9552cbfd6.json b/packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-364421c45be18cc07a7300c9c038f5cac1f47552c7ac98cd8d1ed4d9552cbfd6.json new file mode 100644 index 00000000000..486a4829042 --- /dev/null +++ b/packages/rs-sdk/tests/vectors/test_sync_address_balances/quorum_pubkey-106-364421c45be18cc07a7300c9c038f5cac1f47552c7ac98cd8d1ed4d9552cbfd6.json @@ -0,0 +1 @@ +a7237782fa99ef5752f9f6c63baef59979305ac405db3746276f7ec4386b94a1e255a97835c106f67e2848d70c1f1eb3 \ No newline at end of file