diff --git a/depends/packages/libcurl.mk b/depends/packages/libcurl.mk index 6376e37653..2a3e5ddce6 100644 --- a/depends/packages/libcurl.mk +++ b/depends/packages/libcurl.mk @@ -15,6 +15,9 @@ define $(package)_set_vars $(package)_config_opts += --disable-tftp --without-brotli --without-zstd --without-libidn2 $(package)_config_opts += --without-libpsl --without-nghttp2 --disable-dependency-tracking $(package)_config_opts_linux=--with-pic + # -D_GNU_SOURCE exposes POSIX functions (fileno, fdopen) that libcurl's + # fopen.c needs but are hidden by the depends system's strict -std=c11. + $(package)_cppflags_linux=-D_GNU_SOURCE $(package)_config_env_linux=LIBS="-ldl -lpthread" $(package)_config_opts_mingw32=--with-pic $(package)_config_env_mingw32=LIBS="-lws2_32 -lcrypt32" diff --git a/depends/packages/openssl.mk b/depends/packages/openssl.mk index 1bbcb671cf..5780089a92 100644 --- a/depends/packages/openssl.mk +++ b/depends/packages/openssl.mk @@ -5,15 +5,31 @@ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=cf3098950cb4d853ad95c0841f1f9c6d3dc102dccfcacd521d93925208b76ac8 define $(package)_set_vars - $(package)_config_env=AR="$($(package)_ar)" ARFLAGS=$($(package)_arflags) RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" + # Do NOT export ARFLAGS in config_env. The depends system never defines + # $(package)_arflags, so it expands to empty string. When CFLAGS/CPPFLAGS + # are passed as VAR=value (no positional flags), OpenSSL's Configure sets + # $anyuseradd=false and falls back to reading env vars. An empty ARFLAGS + # in the environment overrides the target default ("r"), producing a + # Makefile with ARFLAGS= (empty), which causes "ar: two different + # operation options specified" at build time. + $(package)_config_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" $(package)_config_env_android=ANDROID_NDK_ROOT=$(host_prefix)/native $(package)_config_opts=no-capieng no-dso no-dtls1 no-ec_nistp_64_gcc_128 no-gost $(package)_config_opts+=no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-shared $(package)_config_opts+=no-ssl-trace no-ssl2 no-ssl3 no-tests no-unit-test no-weak-ssl-ciphers $(package)_config_opts+=no-zlib no-zlib-dynamic no-static-engine no-comp no-afalgeng - $(package)_config_opts+=no-engine no-hw no-asm $($(package)_cflags) $($(package)_cppflags) - $(package)_config_opts_linux=-fPIC -D_GNU_SOURCE - $(package)_config_opts_freebsd=-fPIC + $(package)_config_opts+=no-engine no-hw no-asm + # Pass compiler flags as VAR=value assignments (per OpenSSL INSTALL docs) + # rather than positional args, because multi-word flags like "-arch arm64" + # get split by the shell and OpenSSL's Configure misparses the second word + # as a target name, causing "target already defined" errors on ARM64 macOS. + # Use $$ for deferred evaluation so OS-specific flags (cflags_linux, etc.) + # appended by funcs.mk after set_vars runs are included in the expansion. + $(package)_config_opts+=CFLAGS="$$($(package)_cflags)" + $(package)_config_opts+=CPPFLAGS="$$($(package)_cppflags)" + $(package)_cflags_linux=-fPIC + $(package)_cppflags_linux=-D_GNU_SOURCE + $(package)_cflags_freebsd=-fPIC $(package)_config_opts_x86_64_linux=linux-x86_64 $(package)_config_opts_i686_linux=linux-generic32 $(package)_config_opts_arm_linux=linux-generic32 diff --git a/src/digidollar/validation.cpp b/src/digidollar/validation.cpp index 8ea1ad5962..9ef339c0b8 100644 --- a/src/digidollar/validation.cpp +++ b/src/digidollar/validation.cpp @@ -5,6 +5,7 @@ #include #include #include +#include // Phase 1 metadata tracking support using DigiDollar::ScriptMetadata; @@ -870,7 +871,18 @@ bool ValidateMintTransaction(const CTransaction& tx, LogPrintf("DigiDollar: Invalid DD amount: %d\n", ddAmount); return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-dd-amount"); } - totalDD += ddAmount; + // If totalDD was already set from OP_RETURN, verify consistency + // rather than double-counting the DD amount. + if (totalDD > 0) { + if (ddAmount != totalDD) { + LogPrintf("DigiDollar: DD amount mismatch: token output=%lld, OP_RETURN=%lld\n", + (long long)ddAmount, (long long)totalDD); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-dd-amount-mismatch", + "DD token output amount does not match OP_RETURN amount"); + } + } else { + totalDD += ddAmount; + } } // If we can't extract (cross-node validation), we'll calculate after loop } @@ -1001,8 +1013,9 @@ bool ValidateMintTransaction(const CTransaction& tx, // Verify sufficient collateral if (totalCollateral < requiredCollateral) { - LogPrintf("DigiDollar: Insufficient collateral: provided %d, required %d\n", - totalCollateral, requiredCollateral); + LogPrintf("DigiDollar: Insufficient collateral: provided %lld, required %lld (totalDD=%lld, lockPeriod=%lld)\n", + (long long)totalCollateral, (long long)requiredCollateral, + (long long)totalDD, (long long)lockPeriod); return state.Invalid(TxValidationResult::TX_CONSENSUS, "insufficient-collateral"); } diff --git a/src/oracle/bundle_manager.cpp b/src/oracle/bundle_manager.cpp index a2579b6e86..64288bc3a6 100644 --- a/src/oracle/bundle_manager.cpp +++ b/src/oracle/bundle_manager.cpp @@ -454,6 +454,11 @@ CScript OracleBundleManager::CreateOracleScript(const COracleBundle& bundle) con // Per-oracle entries: oracle_id + schnorr_sig for (const auto& msg : bundle.messages) { + // SECURITY (DGB-SEC-004): Defense-in-depth — reject at serialization + if (msg.oracle_id > 255) { + LogPrintf("Oracle: Phase Two rejecting oracle_id %d > 255\n", msg.oracle_id); + return CScript(); + } p2_data.push_back(static_cast(msg.oracle_id & 0xFF)); if (msg.schnorr_sig.size() == 64) { p2_data.insert(p2_data.end(), msg.schnorr_sig.begin(), msg.schnorr_sig.end()); @@ -487,6 +492,11 @@ CScript OracleBundleManager::CreateOracleScript(const COracleBundle& bundle) con compact_data.reserve(17); // Oracle ID (uint8 for Phase One, expandable to uint32 for Phase Two) + // SECURITY (DGB-SEC-004): Defense-in-depth — reject at serialization + if (msg.oracle_id > 255) { + LogPrintf("Oracle: Phase One rejecting oracle_id %d > 255\n", msg.oracle_id); + return CScript(); + } compact_data.push_back(static_cast(msg.oracle_id & 0xFF)); // Price in micro-USD (uint64, little-endian) @@ -1048,6 +1058,15 @@ void OracleBundleManager::UpdateEpochBundle(int32_t epoch) bool OracleBundleManager::IsValidOracleMessage(const COraclePriceMessage& message) const { + // SECURITY (DGB-SEC-004): Reject oracle_id > 255. The on-chain script + // format stores oracle_id as a single byte. Accepting larger IDs would + // silently truncate, causing ID collisions and signature mismatches. + if (message.oracle_id > 255) { + LogPrintf("Oracle: Rejecting message with oracle_id %d > 255 (exceeds 1-byte on-chain format)\n", + message.oracle_id); + return false; + } + // Phase One: Skip chainparams check when min_oracle_count == 1 (testing mode) if (min_oracle_count == 1) { if (!message.IsValid()) return false; diff --git a/src/test/digidollar_validation_tests.cpp b/src/test/digidollar_validation_tests.cpp index b497978904..b788410e73 100644 --- a/src/test/digidollar_validation_tests.cpp +++ b/src/test/digidollar_validation_tests.cpp @@ -37,8 +37,10 @@ struct DigiDollarValidationTestSetup : public TestingSetup { testPubKey = testKey.GetPubKey(); testXOnlyKey = XOnlyPubKey(testPubKey); - // Clear volatility freeze state from previous tests - DigiDollar::Volatility::VolatilityMonitor::ClearFreeze(); + // Clear ALL volatility state from previous test suites. + // ClearFreeze() alone is insufficient — UpdateState() recalculates + // from stale price history and can re-set freeze flags. + DigiDollar::Volatility::VolatilityMonitor::ClearHistory(); // Validation context is initialized in member initializer list } @@ -50,6 +52,22 @@ struct DigiDollarValidationTestSetup : public TestingSetup { int mockSystemCollateral; int mockHeight; DigiDollar::ValidationContext validationContext; + + // Helper: Build a DD mint OP_RETURN output script + // Format: OP_RETURN <"DD"> + CScript MakeDDMintOpReturn(CAmount ddAmount, int64_t lockHeight, int lockTier) { + CScript script; + script << OP_RETURN; + std::vector dd_marker = {'D', 'D'}; + script << dd_marker; + script << CScriptNum(1); // Type = MINT + script << CScriptNum::serialize(ddAmount); + script << CScriptNum::serialize(lockHeight); + script << CScriptNum(lockTier); + std::vector keyData(testXOnlyKey.begin(), testXOnlyKey.end()); + script << keyData; + return script; + } }; // ============================================================================ @@ -313,31 +331,45 @@ BOOST_FIXTURE_TEST_CASE(script_validation_non_dd_script, DigiDollarValidationTes BOOST_FIXTURE_TEST_CASE(transaction_validation_mint_tx, DigiDollarValidationTestSetup) { - // Create a mock mint transaction + // Create a valid mint transaction with all required components: + // - Collateral input + // - DD OP_RETURN with owner pubkey (required for NUMS verification per T1-04b) + // - Collateral output (P2TR with NUMS internal key) + // - DD token output CMutableTransaction mtx; mtx.nVersion = 0x01000770; // DD_TX_MINT (type=1 in bits 24-31, marker=0x0770 in bits 0-15) - // Add collateral input (simplified for test) + // Add collateral input mtx.vin.resize(1); mtx.vin[0].prevout = COutPoint(uint256S("0x1234"), 0); - // Add collateral output + // Set up mint parameters DigiDollar::MintParams params; params.ddAmount = 10000; // $100.00 - params.lockHeight = mockHeight + 30 * 24 * 60 * 4; + params.lockHeight = mockHeight + 30 * 24 * 60 * 4; // 30-day lock params.ownerKey = testXOnlyKey; - params.internalKey = testXOnlyKey; + params.internalKey = DigiDollar::GetCollateralNUMSKey(); params.oracleKeys = DigiDollar::GetOracleKeys(15); CScript collateralScript = DigiDollar::CreateCollateralP2TR(params); CAmount requiredCollateral = (static_cast(params.ddAmount) * COIN * 500 * 100) / mockOraclePrice; - mtx.vout.resize(2); - mtx.vout[0] = CTxOut(requiredCollateral, collateralScript); + // DD OP_RETURN with owner pubkey (required for NUMS verification) + CScript opReturn = CScript() << OP_RETURN + << std::vector{'D', 'D'} + << CScriptNum(1) + << CScriptNum(params.ddAmount) + << CScriptNum(params.lockHeight) + << CScriptNum(1) // lockTier 1 = 30 days + << std::vector(testXOnlyKey.begin(), testXOnlyKey.end()); + + mtx.vout.resize(3); + mtx.vout[0] = CTxOut(0, opReturn); + mtx.vout[1] = CTxOut(requiredCollateral, collateralScript); // Add DD token output CScript ddScript = DigiDollar::CreateDigiDollarP2TR(testXOnlyKey, params.ddAmount); - mtx.vout[1] = CTxOut(0, ddScript); // DD tokens have no DGB value + mtx.vout[2] = CTxOut(0, ddScript); // DD tokens have no DGB value CTransaction tx(mtx); TxValidationState state;