Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
22 changes: 20 additions & 2 deletions src/consensus/digidollar_transaction_validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <util/strencodings.h>

#include <algorithm>
#include <limits>
#include <set>

// =====================================
Expand All @@ -34,11 +35,28 @@ bool ValidateCollateralRatio(CAmount ddAmount, CAmount collateralAmount, CAmount
// collateralAmount is in DGB satoshis (100000000 satoshis = 1 DGB)

// Required USD value = ddAmount * requiredRatio / 100
CAmount requiredUSDCents = (ddAmount * requiredRatio) / 100;
// Overflow protection: ddAmount * requiredRatio can overflow int64_t for large values
CAmount requiredUSDCents;
const CAmount maxSafeDDMul = std::numeric_limits<CAmount>::max() / requiredRatio;
if (ddAmount > maxSafeDDMul) {
// Divide first to avoid overflow, accepting minor precision loss
requiredUSDCents = (ddAmount / 100) * requiredRatio;
} else {
requiredUSDCents = (ddAmount * requiredRatio) / 100;
}

// Collateral value in cents = (collateralAmount / COIN) * oraclePrice
// = collateralAmount * oraclePrice / COIN
CAmount collateralValueCents = (collateralAmount * oraclePrice) / COIN;
// Overflow protection: collateralAmount * oraclePrice can exceed int64_t max
// (e.g. 1M DGB in sats * price 1M = 10^20 > INT64_MAX 9.2*10^18)
CAmount collateralValueCents;
const CAmount maxSafeCollateral = std::numeric_limits<CAmount>::max() / oraclePrice;
if (collateralAmount > maxSafeCollateral) {
// Divide by COIN first, then multiply (matches dca.cpp pattern)
collateralValueCents = (collateralAmount / COIN) * oraclePrice;
} else {
collateralValueCents = (collateralAmount * oraclePrice) / COIN;
}

return collateralValueCents >= requiredUSDCents;
}
Expand Down
42 changes: 36 additions & 6 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 All @@ -23,8 +24,9 @@ using DigiDollar::GetScriptMetadata;
#include <sync.h>
#include <uint256.h>

#include <unordered_map>
#include <limits>
#include <memory>
#include <unordered_map>

namespace DigiDollar {

Expand Down Expand Up @@ -446,9 +448,25 @@ bool ValidateCollateralRatio(CAmount dgbLocked, CAmount ddMinted,
// Oracle price is in micro-USD (1,000,000 = $1.00), DD is in cents
// Convert: (DGB_sats * oracle_micro_usd / COIN) = micro-USD value
// Then: micro-USD / 10000 = cents
CAmount dgbValueMicroUSD = (dgbLocked * ctx.oraclePriceMicroUSD) / COIN;
// Overflow protection: dgbLocked * oraclePriceMicroUSD can exceed int64_t max
CAmount dgbValueMicroUSD;
const CAmount maxSafeDGB = std::numeric_limits<CAmount>::max() / ctx.oraclePriceMicroUSD;
if (dgbLocked > maxSafeDGB) {
dgbValueMicroUSD = (dgbLocked / COIN) * ctx.oraclePriceMicroUSD;
} else {
dgbValueMicroUSD = (dgbLocked * ctx.oraclePriceMicroUSD) / COIN;
}
CAmount dgbValueInCents = dgbValueMicroUSD / 10000; // Convert micro-USD to cents
int actualRatio = (dgbValueInCents * 100) / ddMinted;
// Overflow protection: dgbValueInCents * 100 can overflow for large values
int actualRatio = 0;
if (ddMinted > 0) {
if (dgbValueInCents > std::numeric_limits<CAmount>::max() / 100) {
CAmount ddMintedDiv100 = ddMinted / 100;
actualRatio = (ddMintedDiv100 > 0) ? dgbValueInCents / ddMintedDiv100 : 0;
} else {
actualRatio = (dgbValueInCents * 100) / ddMinted;
}
}

// Get expected ratio for comparison
const auto& ddParams = ctx.params.GetDigiDollarParams();
Expand Down Expand Up @@ -870,7 +888,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 +1030,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
5 changes: 2 additions & 3 deletions src/test/digidollar_redteam_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4130,9 +4130,8 @@ BOOST_AUTO_TEST_CASE(redteam_t2_02g_mixed_dd_and_regular_inputs)
CMutableTransaction feeTx;
feeTx.nVersion = 2; // Regular Bitcoin version
feeTx.vin.push_back(CTxIn(COutPoint(uint256S("ccdd020200000000000000000000000000000000000000000000000000000001"), 0)));
feeTx.vout.push_back(CTxOut(1 * COIN, CScript() << OP_1 << std::vector<unsigned char>(feeKey.GetPubKey().IsCompressed() ?
std::vector<unsigned char>(XOnlyPubKey(feeKey.GetPubKey()).begin(), XOnlyPubKey(feeKey.GetPubKey()).end()) :
std::vector<unsigned char>(32, 0))));
XOnlyPubKey feeXOnly(feeKey.GetPubKey());
feeTx.vout.push_back(CTxOut(1 * COIN, MakeP2TR(feeXOnly)));
CTransactionRef feeTxRef = MakeTransactionRef(feeTx);
uint256 feeTxHash = feeTxRef->GetHash();

Expand Down
61 changes: 60 additions & 1 deletion src/test/digidollar_transaction_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -934,4 +934,63 @@ BOOST_FIXTURE_TEST_CASE(test_dd_opcode_validation, DigiDollarTransactionTestFixt

// Note: Function implementations are now in digidollar_transaction_validation.cpp

BOOST_AUTO_TEST_SUITE_END()
BOOST_FIXTURE_TEST_CASE(test_collateral_ratio_overflow_protection, DigiDollarTransactionTestFixture)
{
// DGB-SEC-001: Verify integer overflow protection in ValidateCollateralRatio.
// collateralAmount * oraclePrice can exceed INT64_MAX for large values.
// e.g. 1M DGB (10^14 sats) * price 1M = 10^20 > INT64_MAX (9.2*10^18)

const CAmount MAX_PRICE = 1000000; // $10 per DGB in cents

// Normal case: should work correctly
BOOST_CHECK(ValidateCollateralRatio(1000, 100 * COIN, 5000, 200));

// Large collateral that would overflow without protection
CAmount largeCollateral = 1000000LL * COIN; // 1 million DGB in satoshis
// 10^14 * 10^6 = 10^20 — exceeds INT64_MAX without overflow protection
// 1M DGB at $10/DGB = $10M value, 200% of $10 = $20 — easily satisfied
BOOST_CHECK(ValidateCollateralRatio(1000, largeCollateral, MAX_PRICE, 200));

// MAX_MONEY collateral — must not crash and should satisfy the ratio
CAmount maxCollateral = MAX_MONEY; // 21 billion DGB (21000000000 * COIN)
BOOST_CHECK(ValidateCollateralRatio(1000, maxCollateral, MAX_PRICE, 200));

// Large ddAmount that could overflow ddAmount * requiredRatio — should be rejected
// since 100 DGB at $50 = $5000 can't possibly collateralize near-max DD
CAmount largeDDAmount = std::numeric_limits<CAmount>::max() / 100; // Near max
BOOST_CHECK(!ValidateCollateralRatio(largeDDAmount, 100 * COIN, 5000, 200));

// Edge case: negative and zero inputs still rejected
BOOST_CHECK(!ValidateCollateralRatio(-1, 100 * COIN, 5000, 200));
BOOST_CHECK(!ValidateCollateralRatio(1000, -1, 5000, 200));
BOOST_CHECK(!ValidateCollateralRatio(1000, 100 * COIN, -1, 200));
}

BOOST_FIXTURE_TEST_CASE(test_collateral_ratio_overflow_precision, DigiDollarTransactionTestFixture)
{
// Verify that the divide-first fallback path produces results consistent
// with the multiply-first path for values near the overflow threshold.
// The maxSafeCollateral boundary is INT64_MAX / oraclePrice.
const CAmount oraclePrice = 5000; // $50.00 per DGB
const CAmount maxSafeCollateral = std::numeric_limits<CAmount>::max() / oraclePrice;

// Just below threshold — uses multiply-first (precise) path
CAmount belowThreshold = maxSafeCollateral - 1;
bool resultBelow = ValidateCollateralRatio(1000, belowThreshold, oraclePrice, 200);

// Just above threshold — uses divide-first (approximate) path
CAmount aboveThreshold = maxSafeCollateral + 1;
bool resultAbove = ValidateCollateralRatio(1000, aboveThreshold, oraclePrice, 200);

// Both should pass since collateral is astronomically larger than required.
// This verifies the two paths agree for values that clearly satisfy the ratio.
BOOST_CHECK(resultBelow);
BOOST_CHECK(resultAbove);

// Verify consistency: a value that fails on the precise path should also
// fail (or be very close) on the approximate path. Use small collateral
// that cannot satisfy the ratio regardless of path.
BOOST_CHECK(!ValidateCollateralRatio(1000000, 1 * COIN, oraclePrice, 200));
}

BOOST_AUTO_TEST_SUITE_END()
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