Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3b8fc64
feat(wallet): add Platform (DIP-9/13/14/15) key derivation helpers
PastaPastaPasta Aug 12, 2026
c6fe429
feat(wallet): add generic per-wallet Platform data records
PastaPastaPasta Aug 12, 2026
01c8a8a
feat(wallet): add DIP-15 friendship keychain import and platform key …
PastaPastaPasta Aug 12, 2026
a67dbc2
fix(wallet): validate parent pubkey before DIP-14 public derivation
PastaPastaPasta Aug 12, 2026
d583a42
fix(wallet): fail platform seed selection when the pinned seed is una…
PastaPastaPasta Aug 12, 2026
2cd5bcd
fix(wallet): preserve friendship descriptor state on re-import
PastaPastaPasta Aug 12, 2026
005459f
fix(wallet): drop platform data from memory only after the database e…
PastaPastaPasta Aug 12, 2026
31637d7
fix(wallet): fail platform seed selection on a malformed seed pin
PastaPastaPasta Aug 12, 2026
3a430b5
fix(wallet): require a full unlock before serving the legacy platform…
PastaPastaPasta Aug 12, 2026
47c0751
test(wallet): pin friendship derivation against rust-dashcore key-wallet
PastaPastaPasta Aug 13, 2026
b78f3cf
chore(wallet): annotate m_platform_data locking, add missing includes
PastaPastaPasta Aug 13, 2026
80a46ec
fix(wallet): skip empty-mnemonic descriptors in platform seed selection
PastaPastaPasta Aug 13, 2026
3f954b4
fix(wallet): reject unknown platform key types before derivation
PastaPastaPasta Aug 13, 2026
b1da3d4
fix(wallet): treat corrupt platform data records as wallet corruption
PastaPastaPasta Aug 13, 2026
d5e41f4
refactor(wallet): drop the unused friendship import label parameter
PastaPastaPasta Aug 13, 2026
d58facb
fix(wallet): keep damaged Platform cache records noncritical on load
PastaPastaPasta Aug 17, 2026
6a92a1c
fix(wallet): validate mnemonics before deriving the platform seed
PastaPastaPasta Aug 17, 2026
449da14
wallet: make the Platform seed provider descriptor-wallet-only
PastaPastaPasta Aug 18, 2026
81ba952
refactor(wallet): confine Platform derivation to key managers
PastaPastaPasta Aug 18, 2026
939ee20
refactor(wallet): avoid serializing friendship xprv
PastaPastaPasta Aug 18, 2026
b2a3c40
fix(wallet): derive Platform keys from descriptor root
PastaPastaPasta Aug 19, 2026
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
4 changes: 3 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2077,7 +2077,9 @@ CPPFLAGS="$CPPFLAGS_TEMP"
if test -n "$use_sanitizers"; then
export SECP_CFLAGS="$SECP_CFLAGS $SANITIZER_CFLAGS"
fi
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --disable-module-ecdh --disable-openssl-tests"
dnl The ECDH module is required by the wallet's Platform key provider
dnl (DashPay contact request encryption, wallet/platformkeys.cpp).
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --enable-module-recovery --enable-module-ecdh --disable-openssl-tests"
AC_CONFIG_SUBDIRS([src/dashbls src/secp256k1])

AC_OUTPUT
Expand Down
3 changes: 3 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,8 @@ BITCOIN_CORE_H = \
wallet/hdchain.h \
wallet/ismine.h \
wallet/load.h \
wallet/platformkeys.h \
wallet/platformtypes.h \
wallet/receive.h \
wallet/rpc/util.h \
wallet/rpc/wallet.h \
Expand Down Expand Up @@ -709,6 +711,7 @@ libbitcoin_wallet_a_SOURCES = \
wallet/hdchain.cpp \
wallet/interfaces.cpp \
wallet/load.cpp \
wallet/platformkeys.cpp \
wallet/receive.cpp \
wallet/rpc/addresses.cpp \
wallet/rpc/backup.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ if ENABLE_WALLET
BITCOIN_TESTS += \
wallet/test/bip39_tests.cpp \
wallet/test/coinjoin_tests.cpp \
wallet/test/platformkeys_tests.cpp \
wallet/test/psbt_wallet_tests.cpp \
wallet/test/spend_tests.cpp \
wallet/test/wallet_tests.cpp \
Expand Down
29 changes: 29 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <util/message.h>
#include <util/result.h>
#include <util/ui_change_type.h>
#include <wallet/platformtypes.h>

#include <cstdint>
#include <functional>
Expand Down Expand Up @@ -142,6 +143,26 @@ class Wallet
//! Sign special transaction payload
virtual bool signSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector<unsigned char>& vchSig) = 0;

//! Derive and return a public Platform key. The mnemonic-owning key
//! manager performs derivation without exposing the wallet's root seed.
virtual wallet::PlatformKeyResult<CPubKey> getPlatformPubKey(const wallet::PlatformKeyRequest& request) = 0;

//! Sign a 32-byte digest with a platform key (compact/recoverable ECDSA,
//! as used by Platform state transitions).
virtual wallet::PlatformKeyResult<std::vector<unsigned char>> signPlatformDigest(const wallet::PlatformKeyRequest& request,
const uint256& digest) = 0;

//! ECDH shared secret between the identity authentication key
//! and a counterparty public key, using the libsecp256k1 ECDH KDF.
virtual wallet::PlatformKeyResult<SecureVector> platformECDHSecret(const wallet::IdentityAuthKey& key,
const CPubKey& counterparty) = 0;

//! Ensure our private DIP-15 receiving chain is imported as a ranged
//! descriptor and return the corresponding public chain. Derivation,
//! descriptor update and result publication are one wallet-locked action.
virtual wallet::PlatformKeyResult<wallet::FriendshipXpub> ensureFriendshipReceivingKeychain(
const wallet::FriendshipKeychainRequest& request) = 0;

//! Return whether wallet has private key.
virtual bool isSpendable(const CScript& script) = 0;
virtual bool isSpendable(const CTxDestination& dest) = 0;
Expand Down Expand Up @@ -170,6 +191,14 @@ class Wallet
//! Save or remove receive request.
virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;

//! Write (or, with an empty value, erase) a generic Platform data
//! record. Records are persisted in the wallet database and travel with
//! backups; they are opaque to the wallet itself.
virtual bool writePlatformData(const std::string& key, const std::vector<unsigned char>& value) = 0;

//! All Platform data records whose key starts with prefix.
virtual std::map<std::string, std::vector<unsigned char>> getPlatformData(const std::string& prefix) = 0;

//! Display address on external signer
virtual bool displayAddress(const CTxDestination& dest) = 0;

Expand Down
45 changes: 45 additions & 0 deletions src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ struct PubkeyProvider

/** Derive a private key, if private data is available in arg. */
virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;

/** Return this key expression's root extended public key, if any. */
virtual bool GetRootExtPubKey(CExtPubKey& key) const { return false; }

/** Return this key expression's root extended private key, if available. */
virtual bool GetRootExtKey(const SigningProvider& arg, CExtKey& key) const { return false; }
};

class OriginPubkeyProvider final : public PubkeyProvider
Expand Down Expand Up @@ -238,6 +244,14 @@ class OriginPubkeyProvider final : public PubkeyProvider
{
return m_provider->GetPrivKey(pos, arg, key);
}
bool GetRootExtPubKey(CExtPubKey& key) const override
{
return m_provider->GetRootExtPubKey(key);
}
bool GetRootExtKey(const SigningProvider& arg, CExtKey& key) const override
{
return m_provider->GetRootExtKey(arg, key);
}
};

/** An object representing a parsed constant public key in a descriptor. */
Expand Down Expand Up @@ -488,6 +502,15 @@ class BIP32PubkeyProvider final : public PubkeyProvider
key = extkey.key;
return true;
}
bool GetRootExtPubKey(CExtPubKey& key) const override
{
key = m_root_extkey;
return true;
}
bool GetRootExtKey(const SigningProvider& arg, CExtKey& key) const override
{
return GetExtKey(arg, key);
}
};

/** Base class for all Descriptor implementations. */
Expand Down Expand Up @@ -597,6 +620,28 @@ class DescriptorImpl : public Descriptor
return AddChecksum(ret);
}

bool GetRootExtPubKey(CExtPubKey& out) const final
{
if (m_pubkey_args.size() == 1 && m_subdescriptor_args.empty()) {
return m_pubkey_args.front()->GetRootExtPubKey(out);
}
if (m_pubkey_args.empty() && m_subdescriptor_args.size() == 1) {
return m_subdescriptor_args.front()->GetRootExtPubKey(out);
}
return false;
}

bool GetRootExtKey(const SigningProvider& provider, CExtKey& out) const final
{
if (m_pubkey_args.size() == 1 && m_subdescriptor_args.empty()) {
return m_pubkey_args.front()->GetRootExtKey(provider, out);
}
if (m_pubkey_args.empty() && m_subdescriptor_args.size() == 1) {
return m_subdescriptor_args.front()->GetRootExtKey(provider, out);
}
return false;
}

bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
{
bool ret = ToStringHelper(&arg, out, StringType::PRIVATE);
Expand Down
8 changes: 8 additions & 0 deletions src/script/descriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ struct Descriptor {
/** Convert the descriptor back to a string, undoing parsing. */
virtual std::string ToString() const = 0;

/** Return the single extended public key at the root of this descriptor's
* key expression, if it has one. */
virtual bool GetRootExtPubKey(CExtPubKey& out) const { return false; }

/** Return the corresponding single extended private key when it is
* available from `provider`. */
virtual bool GetRootExtKey(const SigningProvider& provider, CExtKey& out) const { return false; }

/** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
virtual bool IsSingleType() const = 0;

Expand Down
29 changes: 29 additions & 0 deletions src/test/descriptor_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <key_io.h>
#include <pubkey.h>
#include <script/descriptor.h>
#include <script/sign.h>
Expand All @@ -11,6 +12,7 @@

#include <boost/test/unit_test.hpp>

#include <array>
#include <optional>
#include <string>
#include <vector>
Expand Down Expand Up @@ -280,6 +282,33 @@ void Check(const std::string& prv, const std::string& pub, const std::string& no

BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(descriptor_root_extended_key)
{
const std::array<std::byte, 32> seed{std::byte{1}};
CExtKey root;
root.SetSeed(seed);

FlatSigningProvider keys;
std::string error;
auto descriptor{Parse("sh(pkh(" + EncodeExtKey(root) + "/0/*))", keys, error)};
BOOST_REQUIRE_MESSAGE(descriptor, error);

CExtPubKey root_pub;
BOOST_REQUIRE(descriptor->GetRootExtPubKey(root_pub));
BOOST_CHECK(root_pub == root.Neuter());

CExtKey root_priv;
BOOST_CHECK(!descriptor->GetRootExtKey(DUMMY_SIGNING_PROVIDER, root_priv));
BOOST_REQUIRE(descriptor->GetRootExtKey(keys, root_priv));
BOOST_CHECK(root_priv == root);

FlatSigningProvider constant_keys;
auto constant{Parse("pk(" + HexStr(root.key.GetPubKey()) + ")", constant_keys, error)};
BOOST_REQUIRE_MESSAGE(constant, error);
BOOST_CHECK(!constant->GetRootExtPubKey(root_pub));
BOOST_CHECK(!constant->GetRootExtKey(keys, root_priv));
}

BOOST_AUTO_TEST_CASE(descriptor_test)
{
// Basic single-key compressed
Expand Down
31 changes: 31 additions & 0 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <interfaces/wallet.h>

#include <chain.h>
#include <chainparams.h>
#include <coinjoin/client.h>
#include <consensus/amount.h>
#include <interfaces/chain.h>
Expand Down Expand Up @@ -33,6 +34,7 @@
#include <wallet/wallet.h>
#include <wallet/hdchain.h>
#include <wallet/scriptpubkeyman.h>
#include <wallet/walletutil.h>
#include <evo/deterministicmns.h>
#include <masternode/sync.h>
#include <txdb.h>
Expand Down Expand Up @@ -239,6 +241,25 @@ class WalletImpl : public Wallet
{
return m_wallet->SignSpecialTxPayload(hash, keyid, vchSig);
}
wallet::PlatformKeyResult<CPubKey> getPlatformPubKey(const wallet::PlatformKeyRequest& request) override
{
return m_wallet->GetPlatformPubKey(request);
}
wallet::PlatformKeyResult<std::vector<unsigned char>> signPlatformDigest(const wallet::PlatformKeyRequest& request,
const uint256& digest) override
{
return m_wallet->SignPlatformDigest(request, digest);
}
wallet::PlatformKeyResult<SecureVector> platformECDHSecret(const wallet::IdentityAuthKey& key,
const CPubKey& counterparty) override
{
return m_wallet->PlatformECDHSecret(key, counterparty);
}
wallet::PlatformKeyResult<wallet::FriendshipXpub> ensureFriendshipReceivingKeychain(
const wallet::FriendshipKeychainRequest& request) override
{
return m_wallet->EnsureFriendshipReceivingKeychain(request);
}
bool isSpendable(const CScript& script) override
{
LOCK(m_wallet->cs_wallet);
Expand Down Expand Up @@ -316,6 +337,16 @@ class WalletImpl : public Wallet
return value.empty() ? m_wallet->EraseAddressReceiveRequest(batch, dest, id)
: m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
}
bool writePlatformData(const std::string& key, const std::vector<unsigned char>& value) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->WritePlatformData(key, value);
}
std::map<std::string, std::vector<unsigned char>> getPlatformData(const std::string& prefix) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->GetPlatformData(prefix);
}
bool displayAddress(const CTxDestination& dest) override
{
LOCK(m_wallet->cs_wallet);
Expand Down
Loading