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/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; diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index d9ee197ed1..9112fc82fa 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -105,6 +105,10 @@ bool CCrypter::Decrypt(const std::vector& vchCiphertext, CKeyingM return true; } +// DGB-SEC-006: nIV is Hash(pubkey) — deterministic by design. +// See crypter.h for full security rationale. The first 16 bytes of +// the 32-byte hash are used as the AES-256-CBC initialization vector, +// binding each ciphertext to its corresponding public key. bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector &vchCiphertext) { CCrypter cKeyCrypter; diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 4a1f430a6b..5b5e8ef65d 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -27,6 +27,35 @@ const unsigned int WALLET_CRYPTO_IV_SIZE = 16; * Wallet Private Keys are then encrypted using AES-256-CBC * with the double-sha256 of the public key as the IV, and the * master key's key as the encryption key (see keystore.[ch]). + * + * SECURITY NOTE (DGB-SEC-006): Deterministic IV Design + * ===================================================== + * The IV for private key encryption is the first 16 bytes of + * Hash(pubkey) (double-SHA256), NOT a random nonce. This is an + * intentional design inherited from Bitcoin Core, not a weakness: + * + * 1. Uniqueness: Each private key has a unique public key, so each + * key gets a unique IV. AES-CBC requires IVs to be unique for + * each message encrypted with the same encryption key; they do + * not need to be unpredictable. + * + * 2. Key binding: The IV cryptographically binds each ciphertext to + * its public key. Swapping ciphertexts between keys fails because + * decryption uses the wrong IV, and VerifyPubKey() catches this. + * + * 3. Determinism: Re-encrypting the same key produces identical + * ciphertext. This is acceptable — wallet keys are encrypted once + * with one master key, and determinism enables verification. + * + * 4. Trade-off: Deterministic IVs slightly aid an offline attacker + * who has the encrypted wallet and knows at least one plaintext + * private key. They can test candidate master keys (derived from + * the user's password) by encrypting the known private key with + * Hash(pubkey) as IV and comparing against the stored ciphertext. + * This is a known-plaintext verification shortcut, not a break of + * AES itself, and is mitigated in practice by strong passwords and + * robust key-derivation parameters. The design is retained for + * compatibility and deterministic verification of key material. */ /** Master key for wallet encryption */