Skip to content
3 changes: 3 additions & 0 deletions depends/packages/libcurl.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
24 changes: 20 additions & 4 deletions depends/packages/openssl.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 16 additions & 3 deletions src/digidollar/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <digidollar/validation.h>
#include <digidollar/scripts.h>
#include <digidollar/digidollar.h>
#include <digidollar/health.h>

// Phase 1 metadata tracking support
using DigiDollar::ScriptMetadata;
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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");
}

Expand Down
10 changes: 10 additions & 0 deletions src/oracle/mock_oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,22 @@ MockOracleManager& MockOracleManager::GetInstance()

CAmount MockOracleManager::GetCurrentPrice() const
{
// SECURITY (DGB-SEC-005): Runtime guard — reject on non-REGTEST networks
if (Params().GetChainType() != ChainType::REGTEST) {
return 0;
}
LOCK(cs_price);
return mockPriceMicroUSD;
}

void MockOracleManager::SetMockPrice(CAmount price_micro_usd)
{
// SECURITY (DGB-SEC-005): Runtime guard — mock oracle must only operate in REGTEST
if (Params().GetChainType() != ChainType::REGTEST) {
LogPrintf("MockOracleManager: SECURITY - SetMockPrice rejected on non-REGTEST network\n");
return;
}

LOCK(cs_price);

// Validate price is reasonable
Expand Down
52 changes: 42 additions & 10 deletions src/test/digidollar_validation_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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"> <type=1> <ddAmount> <lockHeight> <lockTier> <ownerXOnlyPubKey>
CScript MakeDDMintOpReturn(CAmount ddAmount, int64_t lockHeight, int lockTier) {
CScript script;
script << OP_RETURN;
std::vector<unsigned char> 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<unsigned char> keyData(testXOnlyKey.begin(), testXOnlyKey.end());
script << keyData;
return script;
}
};

// ============================================================================
Expand Down Expand Up @@ -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<uint64_t>(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<unsigned char>{'D', 'D'}
<< CScriptNum(1)
<< CScriptNum(params.ddAmount)
<< CScriptNum(params.lockHeight)
<< CScriptNum(1) // lockTier 1 = 30 days
<< std::vector<unsigned char>(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;
Expand Down
5 changes: 3 additions & 2 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1827,10 +1827,11 @@ CAmount GetOraclePriceForTransaction(const CTransaction& tx, int nHeight) {
return oracle_price_micro_usd;
}

// For regtest, check the mock oracle
// For regtest only, check the mock oracle
// Mock oracle now returns price directly in micro-USD (no conversion needed)
// e.g., 6500 micro-USD = $0.0065 per DGB
if (MockOracleManager::GetInstance().IsEnabled()) {
// SECURITY FIX (DGB-SEC-005): Guard mock oracle access behind REGTEST check
if (Params().GetChainType() == ChainType::REGTEST && MockOracleManager::GetInstance().IsEnabled()) {
CAmount mock_price_micro_usd = MockOracleManager::GetInstance().GetCurrentPrice();
if (mock_price_micro_usd > 0) {
LogPrintf("DigiDollar: Using mock oracle price: %lld micro-USD ($%.6f)\n",
Expand Down