Skip to content
Open
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ if(only_docs)
return()
endif()

include(deps/dilithium)
include(deps/Boost)

add_subdirectory(external/antithesis-sdk)
Expand Down
1 change: 1 addition & 0 deletions cmake/XrplCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ target_link_libraries(
Xrpl::opts
Xrpl::syslibs
secp256k1::secp256k1
NIH::dilithium2_ref
xrpl.libpb
xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
Expand Down
62 changes: 62 additions & 0 deletions cmake/deps/dilithium.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
include(FetchContent)

ExternalProject_Add(
dilithium_src
PREFIX ${nih_cache_path}
# Pin to an explicit commit, not a moving branch ref. Bumping this SHA
# is a supply-chain decision that must be reviewed; never revert to a
# branch tag here. Upstream:
# https://github.com/Transia-RnD/dilithium/commit/3032292cfd4d94e0df9bd49a0098669ca9166aa1
GIT_REPOSITORY https://github.com/Transia-RnD/dilithium.git
GIT_TAG 3032292cfd4d94e0df9bd49a0098669ca9166aa1
GIT_SHALLOW FALSE
CONFIGURE_COMMAND ""
LOG_BUILD ON
BUILD_IN_SOURCE 0
BUILD_COMMAND
COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/ref <BINARY_DIR>/ref
COMMAND make -C <BINARY_DIR>/ref clean
COMMAND /bin/sh -c "CFLAGS='-DDILITHIUM_MODE=2 -DDILITHIUM_RANDOMIZED_SIGNING' make -C <BINARY_DIR>/ref libdilithium2_ref.a libfips202_ref.a"
INSTALL_COMMAND ""
BUILD_BYPRODUCTS
<BINARY_DIR>/ref/libdilithium2_ref.a
<BINARY_DIR>/ref/libfips202_ref.a
)

ExternalProject_Get_Property(dilithium_src SOURCE_DIR BINARY_DIR)
set(dilithium_src_SOURCE_DIR "${SOURCE_DIR}")
set(dilithium_src_BINARY_DIR "${BINARY_DIR}")

# Include the reference implementation headers from source
include_directories("${dilithium_src_SOURCE_DIR}/ref")

# Create imported targets for each static library using BINARY_DIR
add_library(dilithium::dilithium2_ref STATIC IMPORTED GLOBAL)
set_target_properties(dilithium::dilithium2_ref PROPERTIES
IMPORTED_LOCATION "${dilithium_src_BINARY_DIR}/ref/libdilithium2_ref.a"
INTERFACE_INCLUDE_DIRECTORIES "${dilithium_src_SOURCE_DIR}/ref/"
)

add_library(dilithium::libfips202_ref STATIC IMPORTED GLOBAL)
set_target_properties(dilithium::libfips202_ref PROPERTIES
IMPORTED_LOCATION "${dilithium_src_BINARY_DIR}/ref/libfips202_ref.a"
INTERFACE_INCLUDE_DIRECTORIES "${dilithium_src_SOURCE_DIR}/ref/"
)

# Add dependencies to ensure the external project is built first
add_dependencies(dilithium::dilithium2_ref dilithium_src)
add_dependencies(dilithium::libfips202_ref dilithium_src)

# Note: We do NOT link the Dilithium library's randombytes.c because we provide
# our own thread-safe implementation in src/libxrpl/protocol/SecretKey.cpp
# that uses xrpld's crypto_prng() instead of direct /dev/urandom access.

# Create an interface library that links to the Dilithium libraries
# Note: Link order matters - libraries that provide symbols must come AFTER libraries that use them
target_link_libraries(xrpl_libs INTERFACE
dilithium::dilithium2_ref
dilithium::libfips202_ref
)

# Create alias for convenience
add_library(NIH::dilithium2_ref ALIAS dilithium::dilithium2_ref)
7 changes: 7 additions & 0 deletions include/xrpl/ledger/helpers/NFTokenHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,11 @@ checkTrustlineDeepFrozen(
beast::Journal const j,
Issue const& issue);

TER
transferNFToken(
ApplyView& view,
AccountID const& buyer,
AccountID const& seller,
uint256 const& nftokenID);

} // namespace xrpl::nft
7 changes: 7 additions & 0 deletions include/xrpl/protocol/KeyType.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace xrpl {
enum class KeyType {
Secp256k1 = 0,
Ed25519 = 1,
dilithium = 2,
};

inline std::optional<KeyType>
Expand All @@ -19,6 +20,9 @@ keyTypeFromString(std::string const& s)
if (s == "ed25519")
return KeyType::Ed25519;

if (s == "dilithium")
return KeyType::dilithium;

return {};
}

Expand All @@ -30,6 +34,9 @@ to_string(KeyType type)

if (type == KeyType::Ed25519)
return "ed25519";

if (type == KeyType::dilithium)
return "dilithium";

return "INVALID";
}
Expand Down
25 changes: 15 additions & 10 deletions include/xrpl/protocol/PublicKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ namespace xrpl {
information needed to determine the cryptosystem
parameters used is stored inside the key.

As of this writing two systems are supported:
As of this writing three systems are supported:

secp256k1
ed25519
dilithium

secp256k1 public keys consist of a 33 byte
compressed public key, with the lead byte equal
Expand All @@ -37,14 +38,18 @@ namespace xrpl {
The ed25519 public keys consist of a 1 byte
prefix constant 0xED, followed by 32 bytes of
public key data.

The dilithium public keys will have their own specific format.
*/
class PublicKey
{
protected:
// All the constructed public keys are valid, non-empty and contain 33
// bytes of data.
// Minimum / standard public key size (secp256k1, ed25519).
static constexpr std::size_t kSize = 33;
std::uint8_t buf_[kSize]{}; // should be large enough
// Buffer sized for the largest supported key (dilithium = 1312 bytes).
// Actual length is tracked in size_.
std::uint8_t buf_[1312]{};
std::size_t size_ = 0;

public:
using const_iterator = std::uint8_t const*;
Expand All @@ -69,10 +74,10 @@ class PublicKey
return buf_;
}

static std::size_t
size() noexcept
[[nodiscard]] std::size_t
size() const noexcept
{
return kSize;
return size_;
}

[[nodiscard]] const_iterator
Expand All @@ -90,19 +95,19 @@ class PublicKey
[[nodiscard]] const_iterator
end() const noexcept
{
return buf_ + kSize;
return buf_ + size_;
}

[[nodiscard]] const_iterator
cend() const noexcept
{
return buf_ + kSize;
return buf_ + size_;
}

[[nodiscard]] Slice
slice() const noexcept
{
return {buf_, kSize};
return {buf_, size_};
}

operator Slice() const noexcept
Expand Down
17 changes: 13 additions & 4 deletions include/xrpl/protocol/STValidation.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,13 @@ STValidation::STValidation(SerialIter& sit, LookupNodeID&& lookupNodeID, bool ch
, signingPubKey_([this]() {
auto const spk = getFieldVL(sfSigningPubKey);

if (publicKeyType(makeSlice(spk)) != KeyType::Secp256k1)
// Validations are signed with either the legacy secp256k1 key
// (pre-amendment) or the new dilithium key (post-amendment). Mixed
// sets are expected during the rolling upgrade. Ed25519 has never
// been valid for validations and is still rejected. verifyDigest()
// dispatches per keytype.
auto const kt = publicKeyType(makeSlice(spk));
if (kt != KeyType::Secp256k1 && kt != KeyType::dilithium)
Throw<std::runtime_error>("Invalid public key in validation");

return PublicKey{makeSlice(spk)};
Expand Down Expand Up @@ -193,9 +199,12 @@ STValidation::STValidation(
"xrpl::STValidation::STValidation(PublicKey, SecretKey) : nonzero "
"node");

// First, set our own public key:
if (publicKeyType(pk) != KeyType::Secp256k1)
logicError("We can only use secp256k1 keys for signing validations");
// First, set our own public key. Only secp256k1 (legacy) and dilithium
// (post-quantum) are valid for signing validations; Ed25519 has never
// been supported here.
if (auto const kt = publicKeyType(pk);
kt != KeyType::Secp256k1 && kt != KeyType::dilithium)
logicError("Validation signing requires secp256k1 or dilithium key");

setFieldVL(sfSigningPubKey, pk.slice());
setFieldU32(sfSigningTime, signTime.time_since_epoch().count());
Expand Down
16 changes: 12 additions & 4 deletions include/xrpl/protocol/SecretKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ class SecretKey
static constexpr std::size_t kSize = 32;

private:
std::uint8_t buf_[kSize]{};
// Dilithium secret keys are 2528 bytes; ed25519/secp256k1 are 32.
// Buffer sized for the largest supported key; actual length in size_.
std::uint8_t buf_[2560]{};
std::size_t size_ = 0;

public:
using const_iterator = std::uint8_t const*;
Expand All @@ -38,6 +41,7 @@ class SecretKey
~SecretKey();

SecretKey(std::array<std::uint8_t, kSize> const& data);
SecretKey(std::array<std::uint8_t, 2560> const& data);
SecretKey(Slice const& slice);

[[nodiscard]] std::uint8_t const*
Expand All @@ -49,7 +53,7 @@ class SecretKey
[[nodiscard]] std::size_t
size() const
{
return sizeof(buf_);
return size_;
}

/** Convert the secret key to a hexadecimal string.
Expand All @@ -75,13 +79,13 @@ class SecretKey
[[nodiscard]] const_iterator
end() const noexcept
{
return buf_ + sizeof(buf_);
return buf_ + size_;
}

[[nodiscard]] const_iterator
cend() const noexcept
{
return buf_ + sizeof(buf_);
return buf_ + size_;
}
};

Expand All @@ -108,6 +112,10 @@ toBase58(TokenType type, SecretKey const& sk)
SecretKey
randomSecretKey();

/** Create a secret key using secure random numbers. */
SecretKey
randomSecretKey(KeyType type);

/** Generate a new secret key deterministically. */
SecretKey
generateSecretKey(KeyType type, Seed const& seed);
Expand Down
3 changes: 3 additions & 0 deletions include/xrpl/protocol/detail/features.macro
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.

XRPL_FEATURE(Quantum, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)
XRPL_FIX (PermissionedDomainInvariant, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegationV1_1, Supported::No, VoteBehavior::DefaultNo)
Expand Down
2 changes: 1 addition & 1 deletion include/xrpl/server/Manifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class ManifestCache

@param m Manifest to add

@return `ManifestDisposition::accepted` if successful, or
@return `ManifestDisposition::Accepted` if successful, or
`stale` or `invalid` otherwise

@par Thread Safety
Expand Down
51 changes: 51 additions & 0 deletions src/libxrpl/ledger/helpers/NFTokenHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1095,4 +1095,55 @@ checkTrustlineDeepFrozen(
return tesSUCCESS;
}

TER
transferNFToken(
ApplyView& view,
AccountID const& buyer,
AccountID const& seller,
uint256 const& nftokenID)
{
auto tokenAndPage = nft::findTokenAndPage(view, seller, nftokenID);

if (!tokenAndPage)
return tecINTERNAL; // LCOV_EXCL_LINE

if (auto const ret = nft::removeToken(view, seller, nftokenID, tokenAndPage->page);
!isTesSuccess(ret))
return ret;

auto const sleBuyer = view.read(keylet::account(buyer));
if (!sleBuyer)
return tecINTERNAL; // LCOV_EXCL_LINE

std::uint32_t const buyerOwnerCountBefore = sleBuyer->getFieldU32(sfOwnerCount);

auto const insertRet = nft::insertToken(view, buyer, std::move(tokenAndPage->token));

// if fixNFTokenReserve is enabled, check if the buyer has sufficient
// reserve to own a new object, if their OwnerCount changed.
//
// There was an issue where the buyer accepts a sell offer, the ledger
// didn't check if the buyer has enough reserve, meaning that buyer can get
// NFTs free of reserve.
if (view.rules().enabled(fixNFTokenReserve))
{
// To check if there is sufficient reserve, we cannot use mPriorBalance
// because NFT is sold for a price. So we must use the balance after
// the deduction of the potential offer price. A small caveat here is
// that the balance has already deducted the transaction fee, meaning
// that the reserve requirement is a few drops higher.
auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance);

auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount);
if (buyerOwnerCountAfter > buyerOwnerCountBefore)
{
if (auto const reserve = view.fees().accountReserve(buyerOwnerCountAfter);
buyerBalance < reserve)
return tecINSUFFICIENT_RESERVE;
}
}

return insertRet;
}

} // namespace xrpl::nft
Loading