mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-22 19:57:09 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5933167771 | ||
|
|
47564db6a4 | ||
|
|
1d08aa2e99 |
No files matched your search
@@ -100,6 +100,7 @@ set(SECP256K1_INSTALL TRUE)
|
||||
add_subdirectory(external/secp256k1)
|
||||
add_library(secp256k1::secp256k1 ALIAS secp256k1)
|
||||
add_subdirectory(external/ed25519-donna)
|
||||
add_subdirectory(external/pqov)
|
||||
add_subdirectory(external/antithesis-sdk)
|
||||
find_package(gRPC REQUIRED)
|
||||
find_package(lz4 REQUIRED)
|
||||
|
||||
@@ -1,3 +1,26 @@
|
||||
# PQSignatures (unreleased, amendment, DefaultNo)
|
||||
|
||||
Adds post-quantum transaction signatures using UOV OV(256,119,45) with
|
||||
compressed public keys (NIST additional-signatures round 3 candidate; pqov
|
||||
round-3 parameters). Everything is gated behind the `PQSignatures` amendment.
|
||||
|
||||
- New `SigningPubKey` encodings: inline `0xF0 | type | key` (46,593 bytes) and
|
||||
reference `0xF1 | AccountID` (21 bytes). Signatures are 135 raw bytes.
|
||||
- New ledger object `PublicKey` (`ltPUBLIC_KEY`, 0x004B), keyed by the key's
|
||||
AccountID (`keylet::pqPublicKey`), with no owner and no owner directory. It
|
||||
is created by the first transaction that signs with the key inline, costs one
|
||||
drop per key byte (load-scaled, charged on `tes` and `tec`), and is
|
||||
reference counted like `HookDefinition`: master key, `RegularKey` and
|
||||
`SignerEntry` slots hold references; deleted at zero.
|
||||
- Reference-form signatures are verified at ingress against the open ledger
|
||||
(never cached as bad) and again in preclaim against the applying view.
|
||||
- Hook API: `util_verify` accepts inline and reference PQ keys;
|
||||
`util_keylet` adds `PQ_PUBLIC_KEY` (38). `ledger_entry` adds
|
||||
`pq_public_key`.
|
||||
- Validator keys are unchanged (classical) in this release.
|
||||
- Vendors `external/pqov` with portable + AVX2 (x86-64) / NEON (aarch64)
|
||||
builds selected at run time.
|
||||
|
||||
# Release Notes
|
||||
|
||||
This document contains the release notes for `xahaud`, the reference server implementation of the Xahau protocol. To learn more about how to build, run or update a `xahaud` server, visit https://docs.xahau.network/infrastructure/peering/connect-to-xahau-mainnet
|
||||
|
||||
@@ -63,6 +63,7 @@ target_link_libraries(xrpl.imports.main
|
||||
absl::random_random
|
||||
date::date
|
||||
ed25519::ed25519
|
||||
pqov::pqov
|
||||
secp256k1::secp256k1
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
|
||||
@@ -315,6 +315,7 @@ enum keylet_code : uint32_t {
|
||||
CREDENTIAL = 35,
|
||||
PERMISSIONED_DOMAIN = 36,
|
||||
MANIFEST = 37,
|
||||
PQ_PUBLIC_KEY = 38,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,11 @@ hook(AccountID const& id) noexcept;
|
||||
Keylet
|
||||
hookState(AccountID const& id, uint256 const& key, uint256 const& ns) noexcept;
|
||||
|
||||
/** The ltPUBLIC_KEY object holding the post-quantum public key whose
|
||||
AccountID is `id` (featurePQSignatures). */
|
||||
Keylet
|
||||
pqPublicKey(AccountID const& id) noexcept;
|
||||
|
||||
Keylet
|
||||
hookStateDir(AccountID const& id, uint256 const& ns) noexcept;
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of xahaud: https://github.com/Xahau/xahaud
|
||||
Copyright (c) 2026 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PROTOCOL_PQPUBLICKEY_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_PQPUBLICKEY_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace pq {
|
||||
|
||||
/** Post-quantum public keys (featurePQSignatures).
|
||||
|
||||
Classical keys are 33 bytes and fit inside every transaction. A UOV
|
||||
public key is 46,591 bytes, so it is sent once and stored in the ledger
|
||||
as an ltPUBLIC_KEY object keyed by the key's AccountID; later
|
||||
transactions refer to it by that AccountID.
|
||||
|
||||
Two new encodings may appear wherever a classical public key is accepted
|
||||
(SigningPubKey of a transaction or of a Signers entry, the key argument
|
||||
of the util_verify Hook API):
|
||||
|
||||
inline: 0xF0 | type (1 byte) | raw public key (publicKeyBytes(type))
|
||||
reference: 0xF1 | AccountID (20 bytes)
|
||||
|
||||
The AccountID of a key is calcAccountID() of its *inline* encoding, so a
|
||||
master-key signer's Account is derived from the key exactly as for
|
||||
classical keys, and the type byte is part of what is hashed.
|
||||
|
||||
Signatures are the scheme's raw signature bytes: there is no prefix, the
|
||||
scheme is known from the key.
|
||||
*/
|
||||
|
||||
constexpr std::uint8_t inlinePrefix = 0xF0;
|
||||
constexpr std::uint8_t referencePrefix = 0xF1;
|
||||
|
||||
/** Scheme + parameter set. Values are never reused: a parameter tweak is a
|
||||
new type, so existing ledger objects and signatures stay verifiable. */
|
||||
enum class KeyType : std::uint8_t {
|
||||
/// UOV, OV(256,119,45), compressed public key (pqov round-3 parameters)
|
||||
uovIp = 0x01,
|
||||
};
|
||||
|
||||
constexpr std::size_t referenceBytes = 1 + AccountID::bytes; // 21
|
||||
|
||||
std::optional<KeyType>
|
||||
keyTypeFromByte(std::uint8_t b) noexcept;
|
||||
|
||||
/// Size of the raw public key for a type (46,591 for uovIp)
|
||||
std::size_t
|
||||
publicKeyBytes(KeyType t) noexcept;
|
||||
|
||||
/// Size of a signature for a type (135 for uovIp)
|
||||
std::size_t
|
||||
signatureBytes(KeyType t) noexcept;
|
||||
|
||||
/// Size of the expanded public key kept in the verification cache
|
||||
std::size_t
|
||||
expandedPublicKeyBytes(KeyType t) noexcept;
|
||||
|
||||
/** Returns the key type if `key` is a well-formed inline encoding:
|
||||
prefix, known type, and exactly 2 + publicKeyBytes(type) bytes. */
|
||||
std::optional<KeyType>
|
||||
inlineKeyType(Slice const& key) noexcept;
|
||||
|
||||
inline bool
|
||||
isInlineKey(Slice const& key) noexcept
|
||||
{
|
||||
return inlineKeyType(key).has_value();
|
||||
}
|
||||
|
||||
/// True if `key` is exactly 0xF1 followed by 20 bytes
|
||||
bool
|
||||
isKeyReference(Slice const& key) noexcept;
|
||||
|
||||
/// The AccountID named by a well-formed reference (precondition: isKeyReference)
|
||||
AccountID
|
||||
referenceAccountID(Slice const& ref) noexcept;
|
||||
|
||||
/// The AccountID of a well-formed inline key (precondition: isInlineKey)
|
||||
AccountID
|
||||
calcAccountID(Slice const& inlineKey) noexcept;
|
||||
|
||||
/// Build the inline encoding from a type and the raw key bytes
|
||||
Blob
|
||||
makeInlineKey(KeyType t, Slice const& rawKey);
|
||||
|
||||
/// Build a 21 byte reference
|
||||
Blob
|
||||
makeReference(AccountID const& id);
|
||||
|
||||
/// The raw key bytes of a well-formed inline key
|
||||
inline Slice
|
||||
rawKey(Slice const& inlineKey) noexcept
|
||||
{
|
||||
return Slice(inlineKey.data() + 2, inlineKey.size() - 2);
|
||||
}
|
||||
|
||||
/** Verify `sig` over `msg` with a well-formed inline key.
|
||||
|
||||
Expanded forms of recently used keys are cached process-wide (keyed by
|
||||
the key's AccountID), so repeated verifications against the same key run
|
||||
at the expanded-key speed. Returns false for malformed input. */
|
||||
bool
|
||||
verify(Slice const& inlineKey, Slice const& msg, Slice const& sig) noexcept;
|
||||
|
||||
/** Derive the inline public key from a 32 byte secret seed. Returns
|
||||
std::nullopt on failure. Used by tooling and tests; xahaud never signs. */
|
||||
std::optional<Blob>
|
||||
publicKeyFromSeed(KeyType t, Slice const& seed);
|
||||
|
||||
/** Sign `msg` with a 32 byte secret seed. UOV signatures are salted and are
|
||||
therefore not deterministic. */
|
||||
std::optional<Blob>
|
||||
sign(KeyType t, Slice const& seed, Slice const& msg);
|
||||
|
||||
/// Name of the SIMD variant in use ("avx2", "neon" or "portable")
|
||||
char const*
|
||||
implementationName() noexcept;
|
||||
|
||||
} // namespace pq
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -31,6 +31,9 @@
|
||||
#include <boost/container/flat_set.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -120,9 +123,46 @@ public:
|
||||
@return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
enum class RequireFullyCanonicalSig : bool { no, yes };
|
||||
|
||||
/** Resolves a post-quantum key reference (featurePQSignatures).
|
||||
|
||||
Given the AccountID named by a 0xF1 reference, returns the key's
|
||||
inline encoding (0xF0 | type | key) from a ledger, or std::nullopt
|
||||
if that ledger holds no ltPUBLIC_KEY for it. */
|
||||
using PQKeyLookup = std::function<std::optional<Blob>(AccountID const&)>;
|
||||
|
||||
/** How checkSign() treats a transaction whose SigningPubKey (or a
|
||||
Signers entry's) is a post-quantum key *reference* when no lookup
|
||||
is supplied: the signature cannot be verified without a ledger. */
|
||||
enum class PQReferencePolicy : bool {
|
||||
/// Report "Unverified PQ key reference": caller cannot verify here.
|
||||
reject,
|
||||
/// Treat as verified; the caller guarantees preclaim verifies it
|
||||
/// against the ledger the transaction is applied to.
|
||||
defer
|
||||
};
|
||||
|
||||
/** Check the signature.
|
||||
@param lookup resolves PQ key references; may be empty.
|
||||
@return `true` if valid signature. If invalid, the error message string.
|
||||
*/
|
||||
Expected<void, std::string>
|
||||
checkSign(RequireFullyCanonicalSig requireCanonicalSig, Rules const& rules)
|
||||
const;
|
||||
checkSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules,
|
||||
PQKeyLookup const& lookup = {},
|
||||
PQReferencePolicy policy = PQReferencePolicy::reject) const;
|
||||
|
||||
/** True if the transaction's SigningPubKey or any Signers entry's
|
||||
SigningPubKey is a post-quantum key reference (0xF1 form). */
|
||||
bool
|
||||
usesPQKeyReference() const;
|
||||
|
||||
/** Inline post-quantum keys carried by this transaction: the
|
||||
SigningPubKey and each Signers entry's SigningPubKey in 0xF0 form,
|
||||
with the AccountID each hashes to. */
|
||||
std::vector<std::pair<AccountID, Blob>>
|
||||
inlinePQKeys() const;
|
||||
|
||||
// SQL Functions with metadata.
|
||||
static std::string const&
|
||||
@@ -141,12 +181,18 @@ public:
|
||||
|
||||
private:
|
||||
Expected<void, std::string>
|
||||
checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const;
|
||||
checkSingleSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules,
|
||||
PQKeyLookup const& lookup,
|
||||
PQReferencePolicy policy) const;
|
||||
|
||||
Expected<void, std::string>
|
||||
checkMultiSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules) const;
|
||||
Rules const& rules,
|
||||
PQKeyLookup const& lookup,
|
||||
PQReferencePolicy policy) const;
|
||||
|
||||
STBase*
|
||||
copy(std::size_t n, void* buf) const override;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(PQSignatures, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(RNG, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PWALoader, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (20261005, Supported::yes, VoteBehavior::DefaultYes)
|
||||
|
||||
@@ -150,6 +150,25 @@ LEDGER_ENTRY(ltIMPORT_VLSEQ, 0x0049, ImportVLSequence, import_vlseq, ({
|
||||
DeleteAccount erases it explicitly. AppLoaderSequence equals the owner's
|
||||
AccountRoot AppLoaderSequence as of the AccountSet that wrote it.
|
||||
*/
|
||||
/** featurePQSignatures: a post-quantum public key stored once in the ledger.
|
||||
|
||||
Content-addressed: keyed by the AccountID of the key (see
|
||||
keylet::pqPublicKey and pq::calcAccountID). Like ltHOOK_DEFINITION it has
|
||||
no owner and no owner directory; it is created by the first transaction
|
||||
that signs with the key inline, paid for per byte, reference counted by
|
||||
the AccountRoot / RegularKey / SignerEntry slots that name the key, and
|
||||
deleted when the count reaches zero.
|
||||
|
||||
\sa keylet::pqPublicKey
|
||||
*/
|
||||
LEDGER_ENTRY(ltPUBLIC_KEY, 0x004B, PublicKey, pq_public_key, ({
|
||||
{sfPublicKeyType, soeREQUIRED},
|
||||
{sfPublicKeyData, soeREQUIRED},
|
||||
{sfReferenceCount, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
LEDGER_ENTRY(ltAPP_LOADER, 0x004c, AppLoader, app_loader, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfAppLoader, soeREQUIRED},
|
||||
|
||||
@@ -42,6 +42,7 @@ TYPED_SFIELD(sfTickSize, UINT8, 16)
|
||||
TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17)
|
||||
TYPED_SFIELD(sfHookResult, UINT8, 18)
|
||||
TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19)
|
||||
TYPED_SFIELD(sfPublicKeyType, UINT8, 20)
|
||||
|
||||
// 16-bit integers (common)
|
||||
TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never)
|
||||
@@ -304,6 +305,7 @@ TYPED_SFIELD(sfAppLoader, VL, 96)
|
||||
TYPED_SFIELD(sfHookName, VL, 97)
|
||||
TYPED_SFIELD(sfRemarkValue, VL, 98)
|
||||
TYPED_SFIELD(sfRemarkName, VL, 99)
|
||||
TYPED_SFIELD(sfPublicKeyData, VL, 95)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -24,21 +24,11 @@
|
||||
/**
|
||||
* TRANSACTION(tag, value, name, fields)
|
||||
*
|
||||
* To ease maintenance, you may replace any unneeded values with "..."
|
||||
* e.g. #define TRANSACTION(tag, value, name, ...)
|
||||
*
|
||||
* You must define a transactor class in the `ripple` namespace named `name`,
|
||||
* and include its header alongside the TRANSACTOR definition using this
|
||||
* format:
|
||||
* #if TRANSACTION_INCLUDE
|
||||
* # include <xrpld/app/tx/detail/HEADER.h>
|
||||
* #endif
|
||||
* and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`.
|
||||
*/
|
||||
|
||||
/** This transaction type executes a payment. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Payment.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYMENT, 0, Payment, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
@@ -51,9 +41,6 @@ TRANSACTION(ttPAYMENT, 0, Payment, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates an escrow object. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Escrow.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -75,9 +62,6 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, ({
|
||||
|
||||
|
||||
/** This transaction type adjusts various account settings. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetAccount.h>
|
||||
#endif
|
||||
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, ({
|
||||
{sfEmailHash, soeOPTIONAL},
|
||||
{sfWalletLocator, soeOPTIONAL},
|
||||
@@ -101,9 +85,6 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, ({
|
||||
}))
|
||||
|
||||
/** This transaction type sets or clears an account's "regular key". */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#endif
|
||||
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
|
||||
{sfRegularKey, soeOPTIONAL},
|
||||
}))
|
||||
@@ -111,9 +92,6 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, ({
|
||||
// 6 deprecated
|
||||
|
||||
/** This transaction type creates an offer to trade one asset for another. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CreateOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
||||
{sfTakerPays, soeREQUIRED},
|
||||
{sfTakerGets, soeREQUIRED},
|
||||
@@ -123,9 +101,6 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type cancels existing offers to trade one asset for another. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CancelOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
|
||||
{sfOfferSequence, soeOPTIONAL},
|
||||
{sfOfferID, soeOPTIONAL}, // keylet as alternative to offerseq
|
||||
@@ -134,9 +109,6 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, ({
|
||||
// 9 deprecated
|
||||
|
||||
/** This transaction type creates a new set of tickets. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CreateTicket.h>
|
||||
#endif
|
||||
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
|
||||
{sfTicketCount, soeREQUIRED},
|
||||
}))
|
||||
@@ -146,18 +118,12 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, ({
|
||||
/** This transaction type modifies the signer list associated with an account. */
|
||||
// The SignerEntries are optional because a SignerList is deleted by
|
||||
// setting the SignerQuorum to zero and omitting SignerEntries.
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#endif
|
||||
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, ({
|
||||
{sfSignerQuorum, soeREQUIRED},
|
||||
{sfSignerEntries, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new unidirectional XRP payment channel. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/PayChan.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -185,9 +151,6 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new check. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CreateCheck.h>
|
||||
#endif
|
||||
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfSendMax, soeREQUIRED},
|
||||
@@ -197,9 +160,6 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type cashes an existing check. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CashCheck.h>
|
||||
#endif
|
||||
TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
@@ -207,17 +167,11 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, ({
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing check. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CancelCheck.h>
|
||||
#endif
|
||||
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, ({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type grants or revokes authorization to transfer funds. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||
#endif
|
||||
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
|
||||
{sfAuthorize, soeOPTIONAL},
|
||||
{sfUnauthorize, soeOPTIONAL},
|
||||
@@ -226,9 +180,6 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, ({
|
||||
}))
|
||||
|
||||
/** This transaction type modifies a trustline between two accounts. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetTrust.h>
|
||||
#endif
|
||||
TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
|
||||
{sfLimitAmount, soeOPTIONAL},
|
||||
{sfQualityIn, soeOPTIONAL},
|
||||
@@ -236,9 +187,6 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, ({
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an existing account. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DeleteAccount.h>
|
||||
#endif
|
||||
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
@@ -246,17 +194,11 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, ({
|
||||
}))
|
||||
|
||||
/** This transaction type installs a hook. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetHook.h>
|
||||
#endif
|
||||
TRANSACTION(ttHOOK_SET, 22, SetHook, ({
|
||||
{sfHooks, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction mints a new NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenMint.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
||||
{sfNFTokenTaxon, soeREQUIRED},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
@@ -268,18 +210,12 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, ({
|
||||
}))
|
||||
|
||||
/** This transaction burns (i.e. destroys) an existing NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenBurn.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction creates a new offer to buy or sell an NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -289,17 +225,11 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, ({
|
||||
}))
|
||||
|
||||
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, ({
|
||||
{sfNFTokenOffers, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
|
||||
{sfNFTokenBuyOffer, soeOPTIONAL},
|
||||
{sfNFTokenSellOffer, soeOPTIONAL},
|
||||
@@ -307,18 +237,12 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, ({
|
||||
}))
|
||||
|
||||
/** This transaction claws back issued tokens. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Clawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttCLAWBACK, 30, Clawback, ({
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction claws back tokens from an AMM pool. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMClawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
|
||||
{sfHolder, soeREQUIRED},
|
||||
{sfAsset, soeREQUIRED},
|
||||
@@ -327,9 +251,6 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates an AMM instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfAmount2, soeREQUIRED},
|
||||
@@ -337,9 +258,6 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type deposits into an AMM instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMDeposit.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -351,9 +269,6 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, ({
|
||||
}))
|
||||
|
||||
/** This transaction type withdraws from an AMM instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMWithdraw.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -364,9 +279,6 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, ({
|
||||
}))
|
||||
|
||||
/** This transaction type votes for the trading fee */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMVote.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -374,9 +286,6 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, ({
|
||||
}))
|
||||
|
||||
/** This transaction type bids for the auction slot */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMBid.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_BID, 39, AMMBid, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -386,18 +295,12 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, ({
|
||||
}))
|
||||
|
||||
/** This transaction type deletes AMM in the empty state */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/AMMDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction mints/burns/buys/sells a URI TOKEN */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/URIToken.h>
|
||||
#endif
|
||||
TRANSACTION(ttURITOKEN_MINT, 45, URITokenMint, ({
|
||||
{sfURI, soeREQUIRED},
|
||||
{sfDigest, soeOPTIONAL},
|
||||
@@ -425,9 +328,6 @@ TRANSACTION(ttURITOKEN_CANCEL_SELL_OFFER, 49, URITokenCancelSellOffer, ({
|
||||
}))
|
||||
|
||||
/** This transactions creates a crosschain sequence number */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/XChainBridge.h>
|
||||
#endif
|
||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 50, XChainCreateClaimID, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeREQUIRED},
|
||||
@@ -507,9 +407,6 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 57, XChainCreateBridge, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates or updates a DID */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DID.h>
|
||||
#endif
|
||||
TRANSACTION(ttDID_SET, 58, DIDSet, ({
|
||||
{sfDIDDocument, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
@@ -520,9 +417,6 @@ TRANSACTION(ttDID_SET, 58, DIDSet, ({
|
||||
TRANSACTION(ttDID_DELETE, 59, DIDDelete, ({}))
|
||||
|
||||
/** This transaction type creates an Oracle instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetOracle.h>
|
||||
#endif
|
||||
TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
{sfProvider, soeOPTIONAL},
|
||||
@@ -533,26 +427,17 @@ TRANSACTION(ttORACLE_SET, 60, OracleSet, ({
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an Oracle instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/DeleteOracle.h>
|
||||
#endif
|
||||
TRANSACTION(ttORACLE_DELETE, 61, OracleDelete, ({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type fixes a problem in the ledger state */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/LedgerStateFix.h>
|
||||
#endif
|
||||
TRANSACTION(ttLEDGER_STATE_FIX, 62, LedgerStateFix, ({
|
||||
{sfLedgerFixType, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a MPTokensIssuance instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
|
||||
{sfAssetScale, soeOPTIONAL},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
@@ -561,35 +446,23 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 63, MPTokenIssuanceCreate, ({
|
||||
}))
|
||||
|
||||
/** This transaction type destroys a MPTokensIssuance instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 64, MPTokenIssuanceDestroy, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 65, MPTokenIssuanceSet, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type authorizes a MPToken instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#endif
|
||||
TRANSACTION(ttMPTOKEN_AUTHORIZE, 66, MPTokenAuthorize, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type create an Credential instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Credentials.h>
|
||||
#endif
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 67, CredentialCreate, ({
|
||||
{sfSubject, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
@@ -611,9 +484,6 @@ TRANSACTION(ttCREDENTIAL_DELETE, 69, CredentialDelete, ({
|
||||
}))
|
||||
|
||||
/** This transaction type modify a NFToken */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/NFTokenModify.h>
|
||||
#endif
|
||||
TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
@@ -621,18 +491,12 @@ TRANSACTION(ttNFTOKEN_MODIFY, 70, NFTokenModify, ({
|
||||
}))
|
||||
|
||||
/** This transaction type creates or modifies a Permissioned Domain */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/PermissionedDomainSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 71, PermissionedDomainSet, ({
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfAcceptedCredentials, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes a Permissioned Domain */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
@@ -646,9 +510,6 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
|
||||
open ledger, so a reveal only becomes public after the revealing
|
||||
validator's transaction set is locked.
|
||||
*/
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Change.h>
|
||||
#endif
|
||||
TRANSACTION(ttENTROPY, 89, Entropy, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfPublicKey, soeREQUIRED},
|
||||
@@ -658,26 +519,17 @@ TRANSACTION(ttENTROPY, 89, Entropy, ({
|
||||
|
||||
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
|
||||
makes the SOTemplate reject the format at construction. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetManifest.h>
|
||||
#endif
|
||||
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
|
||||
{sfManifest, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Cron.h>
|
||||
#endif
|
||||
TRANSACTION(ttCRON, 92, Cron, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/* Sechedule an alarm for later */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/CronSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttCRON_SET, 93, CronSet, ({
|
||||
{sfDelaySeconds, soeOPTIONAL},
|
||||
{sfRepeatCount, soeOPTIONAL},
|
||||
@@ -685,9 +537,6 @@ TRANSACTION(ttCRON_SET, 93, CronSet, ({
|
||||
}))
|
||||
|
||||
/* A note attaching transactor that allows the owner or issuer (on a object by object basis) to attach remarks */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/SetRemarks.h>
|
||||
#endif
|
||||
TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
|
||||
{sfObjectID, soeREQUIRED},
|
||||
{sfRemarks, soeREQUIRED},
|
||||
@@ -695,9 +544,6 @@ TRANSACTION(ttREMARKS_SET, 94, SetRemarks, ({
|
||||
|
||||
/* A payment transactor that delivers only the exact amounts specified, creating accounts and TLs as needed
|
||||
* that the sender pays for. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Remit.h>
|
||||
#endif
|
||||
TRANSACTION(ttREMIT, 95, Remit, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmounts, soeOPTIONAL},
|
||||
@@ -712,18 +558,12 @@ TRANSACTION(ttREMIT, 95, Remit, ({
|
||||
/** This transaction can only be used by the genesis account, which is controlled exclusively by
|
||||
* rewards/governance hooks, to print new XRP to be delivered directly to an array of destinations,
|
||||
* according to reward schedule */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/GenesisMint.h>
|
||||
#endif
|
||||
TRANSACTION(ttGENESIS_MINT, 96, GenesisMint, ({
|
||||
{sfGenesisMints, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction accepts a proof of burn from an external network as a basis
|
||||
* for minting according to featureImport */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Import.h>
|
||||
#endif
|
||||
TRANSACTION(ttIMPORT, 97, Import, ({
|
||||
{sfBlob, soeREQUIRED},
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
@@ -731,18 +571,12 @@ TRANSACTION(ttIMPORT, 97, Import, ({
|
||||
|
||||
/** This transaction resets accumulator/counters and claims a reward for holding an average balance
|
||||
* from a specified hook */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/ClaimReward.h>
|
||||
#endif
|
||||
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
{sfClaimCurrency, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpld/app/tx/detail/Invoke.h>
|
||||
#endif
|
||||
TRANSACTION(ttINVOKE, 99, Invoke, ({
|
||||
{sfBlob, soeOPTIONAL},
|
||||
{sfDestination, soeOPTIONAL},
|
||||
|
||||
@@ -95,6 +95,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
RANDOM = 0x526E, // Rn
|
||||
PUBLIC_KEY = LEDGER_NAMESPACE2(0x01, 'K'),
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -205,6 +206,12 @@ hookDefinition(uint256 const& hash) noexcept
|
||||
ltHOOK_DEFINITION, indexHash(LedgerNameSpace::HOOK_DEFINITION, hash)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
pqPublicKey(AccountID const& id) noexcept
|
||||
{
|
||||
return {ltPUBLIC_KEY, indexHash(LedgerNameSpace::PUBLIC_KEY, id)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
hookState(AccountID const& id, uint256 const& key, uint256 const& ns) noexcept
|
||||
{
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of xahaud: https://github.com/Xahau/xahaud
|
||||
Copyright (c) 2026 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/protocol/PQPublicKey.h>
|
||||
#include <xrpl/basics/hardened_hash.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
// pqov shim symbols (external/pqov/pqov_shim.c), one set per SIMD variant.
|
||||
extern "C" {
|
||||
int
|
||||
pqov_portable_verify_cpk(
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen,
|
||||
const std::uint8_t* sig,
|
||||
const std::uint8_t* cpk);
|
||||
int
|
||||
pqov_portable_expand_public_key(std::uint8_t* pk, const std::uint8_t* cpk);
|
||||
int
|
||||
pqov_portable_verify_pk(
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen,
|
||||
const std::uint8_t* sig,
|
||||
const std::uint8_t* pk);
|
||||
int
|
||||
pqov_portable_keypair_from_seed(std::uint8_t* cpk, const std::uint8_t* seed);
|
||||
int
|
||||
pqov_portable_sign_from_seed(
|
||||
std::uint8_t* sig,
|
||||
const std::uint8_t* seed,
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen);
|
||||
#if PQOV_HAVE_AVX2
|
||||
int
|
||||
pqov_avx2_verify_cpk(
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen,
|
||||
const std::uint8_t* sig,
|
||||
const std::uint8_t* cpk);
|
||||
int
|
||||
pqov_avx2_expand_public_key(std::uint8_t* pk, const std::uint8_t* cpk);
|
||||
int
|
||||
pqov_avx2_verify_pk(
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen,
|
||||
const std::uint8_t* sig,
|
||||
const std::uint8_t* pk);
|
||||
int
|
||||
pqov_avx2_keypair_from_seed(std::uint8_t* cpk, const std::uint8_t* seed);
|
||||
int
|
||||
pqov_avx2_sign_from_seed(
|
||||
std::uint8_t* sig,
|
||||
const std::uint8_t* seed,
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen);
|
||||
#endif
|
||||
#if PQOV_HAVE_NEON
|
||||
int
|
||||
pqov_neon_verify_cpk(
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen,
|
||||
const std::uint8_t* sig,
|
||||
const std::uint8_t* cpk);
|
||||
int
|
||||
pqov_neon_expand_public_key(std::uint8_t* pk, const std::uint8_t* cpk);
|
||||
int
|
||||
pqov_neon_verify_pk(
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen,
|
||||
const std::uint8_t* sig,
|
||||
const std::uint8_t* pk);
|
||||
int
|
||||
pqov_neon_keypair_from_seed(std::uint8_t* cpk, const std::uint8_t* seed);
|
||||
int
|
||||
pqov_neon_sign_from_seed(
|
||||
std::uint8_t* sig,
|
||||
const std::uint8_t* seed,
|
||||
const std::uint8_t* msg,
|
||||
std::size_t mlen);
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace ripple {
|
||||
namespace pq {
|
||||
|
||||
namespace {
|
||||
|
||||
// OV(256,119,45)-pkc
|
||||
constexpr std::size_t uovIpCpkBytes = 46591;
|
||||
constexpr std::size_t uovIpPkBytes = 321300;
|
||||
constexpr std::size_t uovIpSigBytes = 135;
|
||||
constexpr std::size_t seedBytes = 32;
|
||||
|
||||
// Number of expanded public keys (321,300 bytes each) kept in memory.
|
||||
// 64 entries is ~20 MB.
|
||||
constexpr std::size_t expandedCacheEntries = 64;
|
||||
|
||||
struct Impl
|
||||
{
|
||||
char const* name;
|
||||
int (*verifyCpk)(
|
||||
const std::uint8_t*,
|
||||
std::size_t,
|
||||
const std::uint8_t*,
|
||||
const std::uint8_t*);
|
||||
int (*expand)(std::uint8_t*, const std::uint8_t*);
|
||||
int (*verifyPk)(
|
||||
const std::uint8_t*,
|
||||
std::size_t,
|
||||
const std::uint8_t*,
|
||||
const std::uint8_t*);
|
||||
int (*keypair)(std::uint8_t*, const std::uint8_t*);
|
||||
int (*sign)(
|
||||
std::uint8_t*,
|
||||
const std::uint8_t*,
|
||||
const std::uint8_t*,
|
||||
std::size_t);
|
||||
};
|
||||
|
||||
Impl const&
|
||||
impl() noexcept
|
||||
{
|
||||
// Chosen once per process. Every variant computes the same function;
|
||||
// only the speed differs.
|
||||
static Impl const chosen = []() -> Impl {
|
||||
#if PQOV_HAVE_AVX2
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
if (__builtin_cpu_supports("avx2") && __builtin_cpu_supports("aes") &&
|
||||
__builtin_cpu_supports("ssse3"))
|
||||
return Impl{
|
||||
"avx2",
|
||||
pqov_avx2_verify_cpk,
|
||||
pqov_avx2_expand_public_key,
|
||||
pqov_avx2_verify_pk,
|
||||
pqov_avx2_keypair_from_seed,
|
||||
pqov_avx2_sign_from_seed};
|
||||
#endif
|
||||
#endif
|
||||
#if PQOV_HAVE_NEON
|
||||
return Impl{
|
||||
"neon",
|
||||
pqov_neon_verify_cpk,
|
||||
pqov_neon_expand_public_key,
|
||||
pqov_neon_verify_pk,
|
||||
pqov_neon_keypair_from_seed,
|
||||
pqov_neon_sign_from_seed};
|
||||
#endif
|
||||
return Impl{
|
||||
"portable",
|
||||
pqov_portable_verify_cpk,
|
||||
pqov_portable_expand_public_key,
|
||||
pqov_portable_verify_pk,
|
||||
pqov_portable_keypair_from_seed,
|
||||
pqov_portable_sign_from_seed};
|
||||
}();
|
||||
return chosen;
|
||||
}
|
||||
|
||||
/** LRU cache of expanded public keys, keyed by the key's AccountID.
|
||||
|
||||
Expanding a compressed UOV key is most of the cost of a verification
|
||||
(roughly 180k of 250k cycles with AVX2, and nearly all of it on the
|
||||
portable build). Validators and active accounts verify against the same
|
||||
few keys over and over, so caching the expanded form makes those
|
||||
verifications run at the classic-key speed. */
|
||||
class ExpandedKeyCache
|
||||
{
|
||||
using Entry = std::pair<AccountID, std::unique_ptr<std::uint8_t[]>>;
|
||||
std::mutex mutex_;
|
||||
std::list<Entry> lru_;
|
||||
std::unordered_map<AccountID, std::list<Entry>::iterator, hardened_hash<>>
|
||||
index_;
|
||||
|
||||
public:
|
||||
// Returns a shared view of the expanded key, expanding (and caching) it
|
||||
// on a miss. Copies out of the cache to avoid holding the lock during
|
||||
// verification; 321 KB memcpy is far cheaper than a key expansion.
|
||||
bool
|
||||
verify(
|
||||
AccountID const& id,
|
||||
Slice const& cpk,
|
||||
Slice const& msg,
|
||||
Slice const& sig)
|
||||
{
|
||||
auto pk = std::make_unique<std::uint8_t[]>(uovIpPkBytes);
|
||||
bool hit = false;
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
if (auto it = index_.find(id); it != index_.end())
|
||||
{
|
||||
lru_.splice(lru_.begin(), lru_, it->second);
|
||||
std::memcpy(pk.get(), it->second->second.get(), uovIpPkBytes);
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
if (!hit)
|
||||
{
|
||||
if (impl().expand(pk.get(), cpk.data()) != 0)
|
||||
return false;
|
||||
auto copy = std::make_unique<std::uint8_t[]>(uovIpPkBytes);
|
||||
std::memcpy(copy.get(), pk.get(), uovIpPkBytes);
|
||||
std::lock_guard lock(mutex_);
|
||||
if (index_.find(id) == index_.end())
|
||||
{
|
||||
lru_.emplace_front(id, std::move(copy));
|
||||
index_[id] = lru_.begin();
|
||||
while (lru_.size() > expandedCacheEntries)
|
||||
{
|
||||
index_.erase(lru_.back().first);
|
||||
lru_.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
return impl().verifyPk(msg.data(), msg.size(), sig.data(), pk.get()) ==
|
||||
0;
|
||||
}
|
||||
};
|
||||
|
||||
ExpandedKeyCache&
|
||||
cache()
|
||||
{
|
||||
static ExpandedKeyCache c;
|
||||
return c;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<KeyType>
|
||||
keyTypeFromByte(std::uint8_t b) noexcept
|
||||
{
|
||||
switch (b)
|
||||
{
|
||||
case static_cast<std::uint8_t>(KeyType::uovIp):
|
||||
return KeyType::uovIp;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t
|
||||
publicKeyBytes(KeyType t) noexcept
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case KeyType::uovIp:
|
||||
return uovIpCpkBytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
signatureBytes(KeyType t) noexcept
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case KeyType::uovIp:
|
||||
return uovIpSigBytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
expandedPublicKeyBytes(KeyType t) noexcept
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case KeyType::uovIp:
|
||||
return uovIpPkBytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::optional<KeyType>
|
||||
inlineKeyType(Slice const& key) noexcept
|
||||
{
|
||||
if (key.size() < 2 || key[0] != inlinePrefix)
|
||||
return std::nullopt;
|
||||
auto const t = keyTypeFromByte(key[1]);
|
||||
if (!t || key.size() != 2 + publicKeyBytes(*t))
|
||||
return std::nullopt;
|
||||
return t;
|
||||
}
|
||||
|
||||
bool
|
||||
isKeyReference(Slice const& key) noexcept
|
||||
{
|
||||
return key.size() == referenceBytes && key[0] == referencePrefix;
|
||||
}
|
||||
|
||||
AccountID
|
||||
referenceAccountID(Slice const& ref) noexcept
|
||||
{
|
||||
AccountID id;
|
||||
std::memcpy(id.data(), ref.data() + 1, AccountID::bytes);
|
||||
return id;
|
||||
}
|
||||
|
||||
AccountID
|
||||
calcAccountID(Slice const& inlineKey) noexcept
|
||||
{
|
||||
static_assert(AccountID::bytes == sizeof(ripesha_hasher::result_type));
|
||||
ripesha_hasher rsh;
|
||||
rsh(inlineKey.data(), inlineKey.size());
|
||||
return AccountID{static_cast<ripesha_hasher::result_type>(rsh)};
|
||||
}
|
||||
|
||||
Blob
|
||||
makeInlineKey(KeyType t, Slice const& rawKey)
|
||||
{
|
||||
Blob b;
|
||||
b.reserve(2 + rawKey.size());
|
||||
b.push_back(inlinePrefix);
|
||||
b.push_back(static_cast<std::uint8_t>(t));
|
||||
b.insert(b.end(), rawKey.data(), rawKey.data() + rawKey.size());
|
||||
return b;
|
||||
}
|
||||
|
||||
Blob
|
||||
makeReference(AccountID const& id)
|
||||
{
|
||||
Blob b;
|
||||
b.reserve(referenceBytes);
|
||||
b.push_back(referencePrefix);
|
||||
b.insert(b.end(), id.data(), id.data() + AccountID::bytes);
|
||||
return b;
|
||||
}
|
||||
|
||||
bool
|
||||
verify(Slice const& inlineKey, Slice const& msg, Slice const& sig) noexcept
|
||||
{
|
||||
auto const t = inlineKeyType(inlineKey);
|
||||
if (!t || sig.size() != signatureBytes(*t))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
switch (*t)
|
||||
{
|
||||
case KeyType::uovIp:
|
||||
return cache().verify(
|
||||
calcAccountID(inlineKey), rawKey(inlineKey), msg, sig);
|
||||
}
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<Blob>
|
||||
publicKeyFromSeed(KeyType t, Slice const& seed)
|
||||
{
|
||||
if (t != KeyType::uovIp || seed.size() != seedBytes)
|
||||
return std::nullopt;
|
||||
Blob raw(uovIpCpkBytes);
|
||||
if (impl().keypair(raw.data(), seed.data()) != 0)
|
||||
return std::nullopt;
|
||||
return makeInlineKey(t, makeSlice(raw));
|
||||
}
|
||||
|
||||
std::optional<Blob>
|
||||
sign(KeyType t, Slice const& seed, Slice const& msg)
|
||||
{
|
||||
if (t != KeyType::uovIp || seed.size() != seedBytes)
|
||||
return std::nullopt;
|
||||
Blob sig(uovIpSigBytes);
|
||||
if (impl().sign(sig.data(), seed.data(), msg.data(), msg.size()) != 0)
|
||||
return std::nullopt;
|
||||
return sig;
|
||||
}
|
||||
|
||||
char const*
|
||||
implementationName() noexcept
|
||||
{
|
||||
return impl().name;
|
||||
}
|
||||
|
||||
} // namespace pq
|
||||
} // namespace ripple
|
||||
+147
-36
@@ -25,6 +25,7 @@
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/HashPrefix.h>
|
||||
#include <xrpl/protocol/PQPublicKey.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
@@ -212,7 +213,9 @@ STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
Expected<void, std::string>
|
||||
STTx::checkSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules) const
|
||||
Rules const& rules,
|
||||
PQKeyLookup const& lookup,
|
||||
PQReferencePolicy policy) const
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -221,8 +224,8 @@ STTx::checkSign(
|
||||
// multi-signing. Otherwise we're single-signing.
|
||||
Blob const& signingPubKey = getFieldVL(sfSigningPubKey);
|
||||
return signingPubKey.empty()
|
||||
? checkMultiSign(requireCanonicalSig, rules)
|
||||
: checkSingleSign(requireCanonicalSig);
|
||||
? checkMultiSign(requireCanonicalSig, rules, lookup, policy)
|
||||
: checkSingleSign(requireCanonicalSig, rules, lookup, policy);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
@@ -230,6 +233,103 @@ STTx::checkSign(
|
||||
return Unexpected("Internal signature check failure.");
|
||||
}
|
||||
|
||||
bool
|
||||
STTx::usesPQKeyReference() const
|
||||
{
|
||||
if (pq::isKeyReference(makeSlice(getSigningPubKey())))
|
||||
return true;
|
||||
if (isFieldPresent(sfSigners))
|
||||
for (auto const& signer : getFieldArray(sfSigners))
|
||||
if (signer.isFieldPresent(sfSigningPubKey) &&
|
||||
pq::isKeyReference(
|
||||
makeSlice(signer.getFieldVL(sfSigningPubKey))))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::pair<AccountID, Blob>>
|
||||
STTx::inlinePQKeys() const
|
||||
{
|
||||
std::vector<std::pair<AccountID, Blob>> keys;
|
||||
auto consider = [&keys](Blob const& spk) {
|
||||
auto const s = makeSlice(spk);
|
||||
if (pq::isInlineKey(s))
|
||||
keys.emplace_back(pq::calcAccountID(s), spk);
|
||||
};
|
||||
consider(getSigningPubKey());
|
||||
if (isFieldPresent(sfSigners))
|
||||
for (auto const& signer : getFieldArray(sfSigners))
|
||||
if (signer.isFieldPresent(sfSigningPubKey))
|
||||
consider(signer.getFieldVL(sfSigningPubKey));
|
||||
return keys;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/** Verify one signature with a key in any of the supported encodings.
|
||||
|
||||
Returns an error string, or std::nullopt when the signature verified
|
||||
(or was deferred under PQReferencePolicy::defer). */
|
||||
std::optional<std::string>
|
||||
verifyAnyKey(
|
||||
Blob const& spk,
|
||||
Slice const& data,
|
||||
Blob const& signature,
|
||||
bool fullyCanonical,
|
||||
bool isWildcardNetwork,
|
||||
Rules const& rules,
|
||||
STTx::PQKeyLookup const& lookup,
|
||||
STTx::PQReferencePolicy policy)
|
||||
{
|
||||
auto const key = makeSlice(spk);
|
||||
|
||||
if (publicKeyType(key))
|
||||
{
|
||||
if (isWildcardNetwork ||
|
||||
verify(PublicKey(key), data, makeSlice(signature), fullyCanonical))
|
||||
return std::nullopt;
|
||||
return std::string("Invalid signature.");
|
||||
}
|
||||
|
||||
if (!rules.enabled(featurePQSignatures))
|
||||
return std::string("Invalid signature.");
|
||||
|
||||
if (pq::isInlineKey(key))
|
||||
{
|
||||
if (isWildcardNetwork || pq::verify(key, data, makeSlice(signature)))
|
||||
return std::nullopt;
|
||||
return std::string("Invalid signature.");
|
||||
}
|
||||
|
||||
if (pq::isKeyReference(key))
|
||||
{
|
||||
if (isWildcardNetwork)
|
||||
return std::nullopt;
|
||||
if (!lookup)
|
||||
{
|
||||
if (policy == STTx::PQReferencePolicy::defer)
|
||||
return std::nullopt;
|
||||
return std::string("Unverified PQ key reference.");
|
||||
}
|
||||
auto const resolved = lookup(pq::referenceAccountID(key));
|
||||
if (!resolved)
|
||||
return std::string("Unknown PQ key reference.");
|
||||
auto const inlineKey = makeSlice(*resolved);
|
||||
// The ledger (or whoever answered the lookup) is not trusted: the
|
||||
// key must hash to the AccountID the reference names.
|
||||
if (!pq::isInlineKey(inlineKey) ||
|
||||
pq::calcAccountID(inlineKey) != pq::referenceAccountID(key))
|
||||
return std::string("PQ key reference does not match key.");
|
||||
if (pq::verify(inlineKey, data, makeSlice(signature)))
|
||||
return std::nullopt;
|
||||
return std::string("Invalid signature.");
|
||||
}
|
||||
|
||||
return std::string("Invalid signature.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Json::Value
|
||||
STTx::getJson(JsonOptions options) const
|
||||
{
|
||||
@@ -310,7 +410,11 @@ STTx::getMetaSQL(
|
||||
}
|
||||
|
||||
Expected<void, std::string>
|
||||
STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
|
||||
STTx::checkSingleSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules,
|
||||
PQKeyLookup const& lookup,
|
||||
PQReferencePolicy policy) const
|
||||
{
|
||||
// We don't allow both a non-empty sfSigningPubKey and an sfSigners.
|
||||
// That would allow the transaction to be signed two ways. So if both
|
||||
@@ -322,33 +426,33 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
|
||||
bool const isWildcardNetwork =
|
||||
isFieldPresent(sfNetworkID) && getFieldU32(sfNetworkID) == 65535;
|
||||
|
||||
bool validSig = false;
|
||||
std::optional<std::string> failure = "Invalid signature.";
|
||||
try
|
||||
{
|
||||
bool const fullyCanonical = (getFlags() & tfFullyCanonicalSig) ||
|
||||
(requireCanonicalSig == RequireFullyCanonicalSig::yes);
|
||||
|
||||
auto const spk = getFieldVL(sfSigningPubKey);
|
||||
Blob const signature = getFieldVL(sfTxnSignature);
|
||||
Blob const data = getSigningData(*this);
|
||||
|
||||
if (publicKeyType(makeSlice(spk)))
|
||||
{
|
||||
Blob const signature = getFieldVL(sfTxnSignature);
|
||||
Blob const data = getSigningData(*this);
|
||||
|
||||
validSig = isWildcardNetwork ||
|
||||
verify(PublicKey(makeSlice(spk)),
|
||||
makeSlice(data),
|
||||
makeSlice(signature),
|
||||
fullyCanonical);
|
||||
}
|
||||
failure = verifyAnyKey(
|
||||
spk,
|
||||
makeSlice(data),
|
||||
signature,
|
||||
fullyCanonical,
|
||||
isWildcardNetwork,
|
||||
rules,
|
||||
lookup,
|
||||
policy);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
// Assume it was a signature failure.
|
||||
validSig = false;
|
||||
failure = "Invalid signature.";
|
||||
}
|
||||
if (validSig == false)
|
||||
return Unexpected("Invalid signature.");
|
||||
if (failure)
|
||||
return Unexpected(*failure);
|
||||
// Signature was verified.
|
||||
return {};
|
||||
}
|
||||
@@ -356,7 +460,9 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
|
||||
Expected<void, std::string>
|
||||
STTx::checkMultiSign(
|
||||
RequireFullyCanonicalSig requireCanonicalSig,
|
||||
Rules const& rules) const
|
||||
Rules const& rules,
|
||||
PQKeyLookup const& lookup,
|
||||
PQReferencePolicy policy) const
|
||||
{
|
||||
// Make sure the MultiSigners are present. Otherwise they are not
|
||||
// attempting multi-signing and we just have a bad SigningPubKey.
|
||||
@@ -413,35 +519,40 @@ STTx::checkMultiSign(
|
||||
lastAccountID = accountID;
|
||||
|
||||
// Verify the signature.
|
||||
bool validSig = false;
|
||||
std::optional<std::string> failure = "Invalid signature.";
|
||||
try
|
||||
{
|
||||
Serializer s = dataStart;
|
||||
finishMultiSigningData(accountID, s);
|
||||
|
||||
auto spk = signer.getFieldVL(sfSigningPubKey);
|
||||
Blob const signature = signer.getFieldVL(sfTxnSignature);
|
||||
|
||||
if (publicKeyType(makeSlice(spk)))
|
||||
{
|
||||
Blob const signature = signer.getFieldVL(sfTxnSignature);
|
||||
|
||||
// wildcard network gets a free pass
|
||||
validSig = isWildcardNetwork ||
|
||||
verify(PublicKey(makeSlice(spk)),
|
||||
s.slice(),
|
||||
makeSlice(signature),
|
||||
fullyCanonical);
|
||||
}
|
||||
failure = verifyAnyKey(
|
||||
spk,
|
||||
s.slice(),
|
||||
signature,
|
||||
fullyCanonical,
|
||||
isWildcardNetwork,
|
||||
rules,
|
||||
lookup,
|
||||
policy);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
// We assume any problem lies with the signature.
|
||||
validSig = false;
|
||||
failure = "Invalid signature.";
|
||||
}
|
||||
if (!validSig)
|
||||
if (failure)
|
||||
{
|
||||
if (*failure == "Invalid signature.")
|
||||
return Unexpected(
|
||||
std::string("Invalid signature on account ") +
|
||||
toBase58(accountID) + ".");
|
||||
return Unexpected(
|
||||
std::string("Invalid signature on account ") +
|
||||
toBase58(accountID) + ".");
|
||||
*failure + std::string(" Account ") + toBase58(accountID) +
|
||||
".");
|
||||
}
|
||||
}
|
||||
// All signatures verified.
|
||||
return {};
|
||||
|
||||
@@ -1271,79 +1271,6 @@ public:
|
||||
BEAST_EXPECT(table->needValidatedLedger(257));
|
||||
}
|
||||
|
||||
void
|
||||
testIgnoredUnsupported()
|
||||
{
|
||||
testcase("ignore_amendments");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
weeks constexpr w(1);
|
||||
test::jtx::Env env{*this, makeConfig()};
|
||||
|
||||
static std::vector<AmendmentTable::FeatureInfo> const supported =
|
||||
makeDefaultYes(yes_);
|
||||
|
||||
// Ignore every unsupported_ amendment except the first one.
|
||||
BEAST_EXPECT(unsupported_.size() > 1);
|
||||
std::vector<std::string> const ignoredNames(
|
||||
unsupported_.begin() + 1, unsupported_.end());
|
||||
|
||||
auto table = make_AmendmentTable(
|
||||
env.app(),
|
||||
w,
|
||||
supported,
|
||||
emptySection_,
|
||||
emptySection_,
|
||||
journal_,
|
||||
makeSection(ignoredNames));
|
||||
|
||||
for (auto const& a : ignoredNames)
|
||||
BEAST_EXPECT(table->isSupported(amendmentId(a)));
|
||||
BEAST_EXPECT(!table->isSupported(amendmentId(unsupported_[0])));
|
||||
|
||||
majorityAmendments_t const majority;
|
||||
|
||||
// Enabling only ignored amendments must not block.
|
||||
std::set<uint256> enabled;
|
||||
for (auto const& a : ignoredNames)
|
||||
enabled.insert(amendmentId(a));
|
||||
table->doValidatedLedger(1, enabled, majority);
|
||||
BEAST_EXPECT(!table->hasUnsupportedEnabled());
|
||||
BEAST_EXPECT(table->unsupportedAmendments().empty());
|
||||
|
||||
// An ignored amendment must never be voted for.
|
||||
auto const desired = table->getDesired();
|
||||
for (auto const& a : ignoredNames)
|
||||
BEAST_EXPECT(
|
||||
std::find(desired.begin(), desired.end(), amendmentId(a)) ==
|
||||
desired.end());
|
||||
|
||||
// A non-ignored unsupported amendment still blocks.
|
||||
enabled.insert(amendmentId(unsupported_[0]));
|
||||
table->doValidatedLedger(2, enabled, majority);
|
||||
BEAST_EXPECT(table->hasUnsupportedEnabled());
|
||||
|
||||
// Bad entries are rejected.
|
||||
Section bad("ignore_amendments");
|
||||
bad.append("NotARealFeatureName");
|
||||
try
|
||||
{
|
||||
make_AmendmentTable(
|
||||
env.app(),
|
||||
w,
|
||||
supported,
|
||||
emptySection_,
|
||||
emptySection_,
|
||||
journal_,
|
||||
bad);
|
||||
fail("expected bad [ignore_amendments] entry to throw");
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
pass();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFeature(FeatureBitset const& feat)
|
||||
{
|
||||
@@ -1367,7 +1294,6 @@ public:
|
||||
testBadConfig();
|
||||
testEnableVeto();
|
||||
testHasUnsupported();
|
||||
testIgnoredUnsupported();
|
||||
testFeature(all - fixMajorityCalc);
|
||||
testFeature(all);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/ledger/TransactionMaster.h>
|
||||
#include <xrpld/app/tx/detail/Import.h>
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/ledger/ApplyViewImpl.h>
|
||||
#include <xrpl/protocol/PQPublicKey.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
@@ -43,15 +45,37 @@ Expected<bool, HookReturnCode>
|
||||
HookAPI::util_verify(Slice const& data, Slice const& sig, Slice const& key)
|
||||
const
|
||||
{
|
||||
if (key.size() != 33)
|
||||
return Unexpected(INVALID_KEY);
|
||||
|
||||
if (data.size() == 0)
|
||||
return Unexpected(TOO_SMALL);
|
||||
|
||||
if (sig.size() < 30)
|
||||
return Unexpected(TOO_SMALL);
|
||||
|
||||
auto const& view = hookCtx.applyCtx.view();
|
||||
if (view.rules().enabled(featurePQSignatures) && key.size() != 33)
|
||||
{
|
||||
if (pq::isInlineKey(key))
|
||||
return pq::verify(key, data, sig);
|
||||
|
||||
if (pq::isKeyReference(key))
|
||||
{
|
||||
auto const id = pq::referenceAccountID(key);
|
||||
auto const inlineKey = pqkey::readInlineKey(view, id);
|
||||
if (!inlineKey)
|
||||
return Unexpected(DOESNT_EXIST);
|
||||
// Never trust the stored bytes blindly: they must hash to the
|
||||
// AccountID the reference names.
|
||||
if (pq::calcAccountID(makeSlice(*inlineKey)) != id)
|
||||
return Unexpected(INVALID_KEY);
|
||||
return pq::verify(makeSlice(*inlineKey), data, sig);
|
||||
}
|
||||
|
||||
return Unexpected(INVALID_KEY);
|
||||
}
|
||||
|
||||
if (key.size() != 33)
|
||||
return Unexpected(INVALID_KEY);
|
||||
|
||||
if (!publicKeyType(key))
|
||||
return Unexpected(INVALID_KEY);
|
||||
|
||||
|
||||
@@ -2227,12 +2227,18 @@ DEFINE_HOOK_FUNCTION(
|
||||
case keylet_code::SIGNERS:
|
||||
case keylet_code::ACCOUNT:
|
||||
case keylet_code::HOOK:
|
||||
case keylet_code::PQ_PUBLIC_KEY:
|
||||
case keylet_code::DID: {
|
||||
if (keylet_type == keylet_code::DID)
|
||||
{
|
||||
if (!applyCtx.view().rules().enabled(featureDID))
|
||||
return INVALID_ARGUMENT;
|
||||
}
|
||||
if (keylet_type == keylet_code::PQ_PUBLIC_KEY)
|
||||
{
|
||||
if (!applyCtx.view().rules().enabled(featurePQSignatures))
|
||||
return INVALID_ARGUMENT;
|
||||
}
|
||||
if (a == 0 || b == 0)
|
||||
return INVALID_ARGUMENT;
|
||||
|
||||
@@ -2255,8 +2261,9 @@ DEFINE_HOOK_FUNCTION(
|
||||
? ripple::keylet::signers(id)
|
||||
: keylet_type == keylet_code::OWNER_DIR
|
||||
? ripple::keylet::ownerDir(id)
|
||||
: keylet_type == keylet_code::DID
|
||||
? ripple::keylet::did(id)
|
||||
: keylet_type == keylet_code::DID ? ripple::keylet::did(id)
|
||||
: keylet_type == keylet_code::PQ_PUBLIC_KEY
|
||||
? ripple::keylet::pqPublicKey(id)
|
||||
: ripple::keylet::account(id);
|
||||
|
||||
return serialize_keylet(kl, memory, write_ptr, write_len);
|
||||
@@ -3344,8 +3351,10 @@ DEFINE_HOOK_FUNCTION(
|
||||
}
|
||||
|
||||
// Validate either an secp256k1 signature or an ed25519 signature, using the
|
||||
// XRPLD convention for identifying the key type. Pointer prefixes: d = data, s
|
||||
// = signature, k = public key.
|
||||
// XRPLD convention for identifying the key type. With featurePQSignatures the
|
||||
// key may also be a post-quantum key, inline (0xF0 | type | key) or by
|
||||
// reference (0xF1 | AccountID) to an ltPUBLIC_KEY object in the ledger.
|
||||
// Pointer prefixes: d = data, s = signature, k = public key.
|
||||
DEFINE_HOOK_FUNCTION(
|
||||
int64_t,
|
||||
util_verify,
|
||||
|
||||
@@ -1260,29 +1260,13 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
|
||||
|
||||
Section const& upVoted = config_->section(SECTION_AMENDMENTS);
|
||||
|
||||
Section const& ignored = config_->section(SECTION_IGNORE_AMENDMENTS);
|
||||
|
||||
// [ignore_amendments] exists to keep long-lived test networks syncable
|
||||
// after an amendment they activated is removed from the codebase. It
|
||||
// must never be usable on a production network.
|
||||
if (!ignored.lines().empty() &&
|
||||
(config_->NETWORK_ID == 0 || config_->NETWORK_ID == 21337))
|
||||
{
|
||||
JLOG(m_journal.fatal()) << "[" SECTION_IGNORE_AMENDMENTS
|
||||
"] is not permitted on "
|
||||
"network_id "
|
||||
<< config_->NETWORK_ID;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_amendmentTable = make_AmendmentTable(
|
||||
*this,
|
||||
config().AMENDMENT_MAJORITY_TIME,
|
||||
supported,
|
||||
upVoted,
|
||||
downVoted,
|
||||
logs_->journal("Amendments"),
|
||||
ignored);
|
||||
logs_->journal("Amendments"));
|
||||
}
|
||||
|
||||
Pathfinder::initPathTable();
|
||||
|
||||
@@ -211,8 +211,7 @@ make_AmendmentTable(
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored = Section{});
|
||||
beast::Journal journal);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -1169,7 +1169,9 @@ NetworkOPsImp::submitTransaction(std::shared_ptr<STTx const> const& iTrans)
|
||||
app_.getHashRouter(),
|
||||
*trans,
|
||||
m_ledgerMaster.getValidatedRules(),
|
||||
app_.config());
|
||||
app_.config(),
|
||||
tapNONE,
|
||||
app_.openLedger().current());
|
||||
|
||||
if (validity != Validity::Valid)
|
||||
{
|
||||
@@ -1418,7 +1420,9 @@ NetworkOPsImp::processTransaction(
|
||||
app_.getHashRouter(),
|
||||
*transaction->getSTransaction(),
|
||||
view->rules(),
|
||||
app_.config());
|
||||
app_.config(),
|
||||
tapNONE,
|
||||
view);
|
||||
XRPL_ASSERT(
|
||||
validity == Validity::Valid,
|
||||
"ripple::NetworkOPsImp::processTransaction : valid validity");
|
||||
|
||||
@@ -302,15 +302,6 @@ struct AmendmentState
|
||||
/** Indicates an amendment that this server has code support for. */
|
||||
bool supported = false;
|
||||
|
||||
/** Indicates an amendment listed in [ignore_amendments]. The server has
|
||||
no code for it, but must not become amendment blocked if it is (or
|
||||
becomes) enabled on the ledger. Such an amendment is reported as
|
||||
`supported` internally so every blocking check (LedgerMaster,
|
||||
NetworkOPs, Change) passes, but is never voted for and is shown as
|
||||
unsupported + ignored over RPC.
|
||||
*/
|
||||
bool ignored = false;
|
||||
|
||||
/** The name of this amendment, possibly empty. */
|
||||
std::string name;
|
||||
|
||||
@@ -485,8 +476,7 @@ public:
|
||||
std::vector<FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored);
|
||||
beast::Journal journal);
|
||||
|
||||
uint256
|
||||
find(std::string const& name) const override;
|
||||
@@ -553,8 +543,7 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
std::vector<FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored)
|
||||
beast::Journal journal)
|
||||
: lastUpdateSeq_(0)
|
||||
, majorityTime_(majorityTime)
|
||||
, unsupportedEnabled_(false)
|
||||
@@ -597,59 +586,6 @@ AmendmentTableImpl::AmendmentTableImpl(
|
||||
<< " voted by default if not enabled on the ledger.";
|
||||
}
|
||||
|
||||
// Parse ignored amendments from config. Each line is either a registered
|
||||
// feature name (e.g. "HookOnV2") or "<64 hex amendment ID> [name]". These
|
||||
// are deliberately NOT persisted to wallet.db: removing the line from the
|
||||
// config must restore normal amendment-blocking behaviour.
|
||||
for (auto const& line : ignored.lines())
|
||||
{
|
||||
std::string const trimmed = boost::algorithm::trim_copy(line);
|
||||
if (trimmed.empty())
|
||||
continue;
|
||||
|
||||
std::vector<std::string> parts;
|
||||
boost::algorithm::split(
|
||||
parts,
|
||||
trimmed,
|
||||
boost::algorithm::is_space(),
|
||||
boost::algorithm::token_compress_on);
|
||||
|
||||
uint256 id;
|
||||
std::string name;
|
||||
if (id.parseHex(parts[0]))
|
||||
name = parts.size() > 1 ? parts[1] : std::string{};
|
||||
else if (auto const f = getRegisteredFeature(parts[0]))
|
||||
{
|
||||
id = *f;
|
||||
name = parts[0];
|
||||
}
|
||||
else
|
||||
Throw<std::runtime_error>(
|
||||
"Invalid entry '" + line + "' in [" + ignored.name() +
|
||||
"]: expected a registered feature name or amendment ID");
|
||||
|
||||
AmendmentState& s = add(id, lock);
|
||||
if (s.supported)
|
||||
{
|
||||
JLOG(j_.warn()) << "[" << ignored.name() << "] lists amendment "
|
||||
<< id << " (" << s.name
|
||||
<< ") which this server supports; entry has no "
|
||||
"effect.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!name.empty())
|
||||
s.name = name;
|
||||
s.supported = true;
|
||||
s.ignored = true;
|
||||
s.vote = AmendmentVote::obsolete;
|
||||
|
||||
JLOG(j_.warn()) << "Amendment " << id << " (" << s.name
|
||||
<< ") is unsupported but listed in [" << ignored.name()
|
||||
<< "]: this server will NOT become amendment blocked "
|
||||
"if it is enabled on the ledger.";
|
||||
}
|
||||
|
||||
hash_set<uint256> detect_conflict;
|
||||
// Parse enabled amendments from config
|
||||
for (std::pair<uint256, std::string> const& a : parseSection(enabled))
|
||||
@@ -839,12 +775,7 @@ AmendmentTableImpl::enable(uint256 const& amendment)
|
||||
|
||||
s.enabled = true;
|
||||
|
||||
if (s.ignored)
|
||||
{
|
||||
JLOG(j_.warn()) << "Ignored unsupported amendment " << amendment << " ("
|
||||
<< s.name << ") is enabled on the ledger.";
|
||||
}
|
||||
else if (!s.supported)
|
||||
if (!s.supported)
|
||||
{
|
||||
JLOG(j_.error()) << "Unsupported amendment " << amendment
|
||||
<< " activated.";
|
||||
@@ -1113,9 +1044,7 @@ AmendmentTableImpl::injectJson(
|
||||
if (!fs.name.empty())
|
||||
v[jss::name] = fs.name;
|
||||
|
||||
v[jss::supported] = fs.supported && !fs.ignored;
|
||||
if (fs.ignored)
|
||||
v["ignored"] = true;
|
||||
v[jss::supported] = fs.supported;
|
||||
if (!fs.enabled && isAdmin)
|
||||
{
|
||||
if (fs.vote == AmendmentVote::obsolete)
|
||||
@@ -1184,11 +1113,10 @@ make_AmendmentTable(
|
||||
std::vector<AmendmentTable::FeatureInfo> const& supported,
|
||||
Section const& enabled,
|
||||
Section const& vetoed,
|
||||
beast::Journal journal,
|
||||
Section const& ignored)
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::make_unique<AmendmentTableImpl>(
|
||||
app, majorityTime, supported, enabled, vetoed, journal, ignored);
|
||||
app, majorityTime, supported, enabled, vetoed, journal);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -58,6 +58,18 @@ enum class Validity {
|
||||
@return `std::pair`, where `.first` is the status, and
|
||||
`.second` is the reason if appropriate.
|
||||
|
||||
@param pqKeyLedger A ledger used to resolve post-quantum key
|
||||
*references* (featurePQSignatures, SigningPubKey of the form
|
||||
0xF1 | AccountID). Every ingress path (network, RPC submit, sign)
|
||||
must pass its open ledger, so that unverifiable transactions are
|
||||
not relayed. A reference whose key is missing from that ledger is
|
||||
reported SigBad but is NOT cached as bad: the key may exist in the
|
||||
ledger the transaction is eventually applied to. When no ledger is
|
||||
given (the transactor engine's preflight), reference signatures are
|
||||
deferred to preclaim, where Transactor::checkSign verifies them
|
||||
against the view the transaction is applied to; inline keys are
|
||||
verified here as usual.
|
||||
|
||||
@see Validity
|
||||
*/
|
||||
std::pair<Validity, std::string>
|
||||
@@ -66,7 +78,8 @@ checkValidity(
|
||||
STTx const& tx,
|
||||
Rules const& rules,
|
||||
Config const& config,
|
||||
ApplyFlags const flags = tapNONE);
|
||||
ApplyFlags const flags = tapNONE,
|
||||
std::shared_ptr<ReadView const> const& pqKeyLedger = nullptr);
|
||||
|
||||
/** Ingress fee outcome for a manifest-authorized SetManifest.
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <xrpld/app/tx/detail/DeleteOracle.h>
|
||||
#include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
@@ -493,6 +494,14 @@ DeleteAccount::doApply()
|
||||
if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
|
||||
dst->clearFlag(lsfPasswordSpent);
|
||||
|
||||
// featurePQSignatures: release the references held by this account's
|
||||
// master key (if enabled) and regular key slots. The signer list's
|
||||
// entries were released when the list was deleted above.
|
||||
if (!src->isFlag(lsfDisableMaster))
|
||||
pqkey::removeReference(view(), account_, j_);
|
||||
if (src->isFieldPresent(sfRegularKey))
|
||||
pqkey::removeReference(view(), src->getAccountID(sfRegularKey), j_);
|
||||
|
||||
view().update(dst);
|
||||
view().erase(src);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <xrpld/app/misc/Manifest.h>
|
||||
#include <xrpld/app/tx/detail/Import.h>
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
@@ -1193,13 +1194,22 @@ Import::doRegularKey(std::shared_ptr<SLE>& sle, STTx const& stpTrans)
|
||||
JLOG(ctx_.journal.trace()) << "Import: clearing SetRegularKey "
|
||||
<< " acc: " << id;
|
||||
if (sle->isFieldPresent(sfRegularKey))
|
||||
{
|
||||
pqkey::removeReference(
|
||||
ctx_.view(), sle->getAccountID(sfRegularKey), ctx_.journal);
|
||||
sle->makeFieldAbsent(sfRegularKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
AccountID rk = stpTrans.getAccountID(sfRegularKey);
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "Import: actioning SetRegularKey " << rk << " acc: " << id;
|
||||
// featurePQSignatures: keep the key object reference counts in step.
|
||||
std::vector<AccountID> const oldKey = sle->isFieldPresent(sfRegularKey)
|
||||
? std::vector<AccountID>{sle->getAccountID(sfRegularKey)}
|
||||
: std::vector<AccountID>{};
|
||||
pqkey::adjustReferences(ctx_.view(), oldKey, {rk}, ctx_.journal);
|
||||
sle->setAccountID(sfRegularKey, rk);
|
||||
|
||||
// always set this flag if they have done any regular keying
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <xrpl/protocol/AppLoader.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/FeeUnits.h>
|
||||
#include <xrpl/protocol/PQPublicKey.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/SystemParameters.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
@@ -617,6 +618,7 @@ LedgerEntryTypesMatch::visitEntry(
|
||||
case ltPERMISSIONED_DOMAIN:
|
||||
case ltAPP_LOADER:
|
||||
case ltMANIFEST:
|
||||
case ltPUBLIC_KEY:
|
||||
break;
|
||||
default:
|
||||
invalidTypeAdded_ = true;
|
||||
@@ -2080,4 +2082,57 @@ ValidAppLoader::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
ValidPQPublicKey::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
auto const& sle = after ? after : before;
|
||||
if (!sle || sle->getType() != ltPUBLIC_KEY)
|
||||
return;
|
||||
touched_ = true;
|
||||
if (!isDelete)
|
||||
live_.push_back(after);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidPQPublicKey::finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (!touched_)
|
||||
return true;
|
||||
|
||||
auto fail = [&](char const* why) {
|
||||
JLOG(j.fatal()) << "Invariant failed: PublicKey " << why;
|
||||
return false;
|
||||
};
|
||||
|
||||
if (!view.rules().enabled(featurePQSignatures))
|
||||
return fail("changed before featurePQSignatures was enabled");
|
||||
|
||||
for (auto const& sle : live_)
|
||||
{
|
||||
auto const type = pq::keyTypeFromByte((*sle)[sfPublicKeyType]);
|
||||
if (!type)
|
||||
return fail("has an unknown key type");
|
||||
auto const& data = sle->getFieldVL(sfPublicKeyData);
|
||||
if (data.size() != pq::publicKeyBytes(*type))
|
||||
return fail("has key data of the wrong size");
|
||||
auto const id = pq::calcAccountID(
|
||||
makeSlice(pq::makeInlineKey(*type, makeSlice(data))));
|
||||
if (sle->key() != keylet::pqPublicKey(id).key)
|
||||
return fail("is not keyed at the AccountID of its key");
|
||||
if ((*sle)[sfReferenceCount] < 1)
|
||||
return fail("has a zero reference count");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -739,6 +739,35 @@ public:
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: every ltPUBLIC_KEY object is well formed.
|
||||
*
|
||||
* For every ltPUBLIC_KEY created or changed by a transaction:
|
||||
* featurePQSignatures is enabled; its key is the keylet of the AccountID the
|
||||
* stored key hashes to; the key data has the exact size for its type; and
|
||||
* its ReferenceCount is at least one.
|
||||
*/
|
||||
class ValidPQPublicKey
|
||||
{
|
||||
bool touched_ = false;
|
||||
std::vector<std::shared_ptr<SLE const>> live_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
using InvariantChecks = std::tuple<
|
||||
TransactionFeeCheck,
|
||||
AccountRootsNotDeleted,
|
||||
@@ -759,7 +788,8 @@ using InvariantChecks = std::tuple<
|
||||
ValidPermissionedDomain,
|
||||
ValidAMM,
|
||||
ValidLockedBalance,
|
||||
ValidAppLoader>;
|
||||
ValidAppLoader,
|
||||
ValidPQPublicKey>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of xahaud: https://github.com/Xahau/xahaud
|
||||
Copyright (c) 2026 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PQPublicKey.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
namespace ripple {
|
||||
namespace pqkey {
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
read(ReadView const& view, AccountID const& id)
|
||||
{
|
||||
if (!view.rules().enabled(featurePQSignatures))
|
||||
return nullptr;
|
||||
return view.read(keylet::pqPublicKey(id));
|
||||
}
|
||||
|
||||
std::optional<Blob>
|
||||
readInlineKey(ReadView const& view, AccountID const& id)
|
||||
{
|
||||
auto const sle = read(view, id);
|
||||
if (!sle)
|
||||
return std::nullopt;
|
||||
auto const type = pq::keyTypeFromByte(sle->getFieldU8(sfPublicKeyType));
|
||||
if (!type)
|
||||
return std::nullopt;
|
||||
return pq::makeInlineKey(*type, makeSlice(sle->getFieldVL(sfPublicKeyData)));
|
||||
}
|
||||
|
||||
STTx::PQKeyLookup
|
||||
makeLookup(ReadView const& view)
|
||||
{
|
||||
return [&view](AccountID const& id) { return readInlineKey(view, id); };
|
||||
}
|
||||
|
||||
std::uint64_t
|
||||
unrevealedKeyBytes(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
if (!view.rules().enabled(featurePQSignatures))
|
||||
return 0;
|
||||
std::uint64_t bytes = 0;
|
||||
for (auto const& [id, key] : tx.inlinePQKeys())
|
||||
if (!view.exists(keylet::pqPublicKey(id)))
|
||||
bytes += key.size() - 2; // the prefix and type byte are not stored
|
||||
return bytes;
|
||||
}
|
||||
|
||||
TER
|
||||
reveal(ApplyView& view, STTx const& tx, beast::Journal j)
|
||||
{
|
||||
if (!view.rules().enabled(featurePQSignatures))
|
||||
return tesSUCCESS;
|
||||
|
||||
for (auto const& [id, key] : tx.inlinePQKeys())
|
||||
{
|
||||
auto const k = keylet::pqPublicKey(id);
|
||||
if (view.exists(k))
|
||||
continue;
|
||||
|
||||
auto const inlineKey = makeSlice(key);
|
||||
auto const type = pq::inlineKeyType(inlineKey);
|
||||
if (!type)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE: inlinePQKeys() filtered
|
||||
|
||||
auto sle = std::make_shared<SLE>(k);
|
||||
sle->setFieldU8(sfPublicKeyType, static_cast<std::uint8_t>(*type));
|
||||
sle->setFieldVL(sfPublicKeyData, pq::rawKey(inlineKey));
|
||||
sle->setFieldU64(sfReferenceCount, 1);
|
||||
view.insert(sle);
|
||||
|
||||
JLOG(j.trace()) << "PQ public key revealed for " << toBase58(id);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
void
|
||||
addReference(ApplyView& view, AccountID const& id, beast::Journal j)
|
||||
{
|
||||
if (!view.rules().enabled(featurePQSignatures))
|
||||
return;
|
||||
auto sle = view.peek(keylet::pqPublicKey(id));
|
||||
if (!sle)
|
||||
return;
|
||||
sle->setFieldU64(sfReferenceCount, sle->getFieldU64(sfReferenceCount) + 1);
|
||||
view.update(sle);
|
||||
JLOG(j.trace()) << "PQ public key " << toBase58(id) << " refcount +1";
|
||||
}
|
||||
|
||||
void
|
||||
removeReference(ApplyView& view, AccountID const& id, beast::Journal j)
|
||||
{
|
||||
if (!view.rules().enabled(featurePQSignatures))
|
||||
return;
|
||||
auto sle = view.peek(keylet::pqPublicKey(id));
|
||||
if (!sle)
|
||||
return;
|
||||
auto const count = sle->getFieldU64(sfReferenceCount);
|
||||
if (count <= 1)
|
||||
{
|
||||
view.erase(sle);
|
||||
JLOG(j.trace()) << "PQ public key " << toBase58(id) << " deleted";
|
||||
return;
|
||||
}
|
||||
sle->setFieldU64(sfReferenceCount, count - 1);
|
||||
view.update(sle);
|
||||
JLOG(j.trace()) << "PQ public key " << toBase58(id) << " refcount -1";
|
||||
}
|
||||
|
||||
void
|
||||
adjustReferences(
|
||||
ApplyView& view,
|
||||
std::vector<AccountID> before,
|
||||
std::vector<AccountID> after,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!view.rules().enabled(featurePQSignatures))
|
||||
return;
|
||||
auto normalize = [](std::vector<AccountID>& v) {
|
||||
std::sort(v.begin(), v.end());
|
||||
v.erase(std::unique(v.begin(), v.end()), v.end());
|
||||
};
|
||||
normalize(before);
|
||||
normalize(after);
|
||||
|
||||
std::vector<AccountID> removed;
|
||||
std::vector<AccountID> added;
|
||||
std::set_difference(
|
||||
before.begin(),
|
||||
before.end(),
|
||||
after.begin(),
|
||||
after.end(),
|
||||
std::back_inserter(removed));
|
||||
std::set_difference(
|
||||
after.begin(),
|
||||
after.end(),
|
||||
before.begin(),
|
||||
before.end(),
|
||||
std::back_inserter(added));
|
||||
|
||||
for (auto const& id : removed)
|
||||
removeReference(view, id, j);
|
||||
for (auto const& id : added)
|
||||
addReference(view, id, j);
|
||||
}
|
||||
|
||||
} // namespace pqkey
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,101 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of xahaud: https://github.com/Xahau/xahaud
|
||||
Copyright (c) 2026 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_PQKEYLEDGER_H_INCLUDED
|
||||
#define RIPPLE_TX_PQKEYLEDGER_H_INCLUDED
|
||||
|
||||
#include <xrpld/ledger/ApplyView.h>
|
||||
#include <xrpld/ledger/ReadView.h>
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** The ltPUBLIC_KEY ledger object (featurePQSignatures).
|
||||
|
||||
Every function here is a no-op unless featurePQSignatures is enabled in
|
||||
the view's rules, so callers in consensus code need no extra gating.
|
||||
|
||||
Reference counting follows ltHOOK_DEFINITION: it is a garbage-collection
|
||||
hint, not something correctness depends on. The count is incremented
|
||||
when a slot that names the key (AccountRoot master key, RegularKey,
|
||||
SignerEntry) is written while the object exists and decremented when
|
||||
such a slot is cleared while it exists; the object is deleted at zero.
|
||||
A slot written *before* the key was revealed is simply not counted, and
|
||||
the worst case for a stale reference is that the key must be revealed
|
||||
inline again (paying the byte fee again). Verification of a reference
|
||||
always requires the object to be present, so a stale count can never
|
||||
make a bad signature pass.
|
||||
*/
|
||||
namespace pqkey {
|
||||
|
||||
/// The ltPUBLIC_KEY object for the key whose AccountID is `id`, if any
|
||||
std::shared_ptr<SLE const>
|
||||
read(ReadView const& view, AccountID const& id);
|
||||
|
||||
/// The inline (0xF0 | type | key) encoding stored for `id`, if any
|
||||
std::optional<Blob>
|
||||
readInlineKey(ReadView const& view, AccountID const& id);
|
||||
|
||||
/** A lookup for STTx::checkSign that resolves references against `view`.
|
||||
The view must outlive the returned function. */
|
||||
STTx::PQKeyLookup
|
||||
makeLookup(ReadView const& view);
|
||||
|
||||
/** Bytes of inline PQ keys in `tx` that `view` does not yet hold an
|
||||
object for. The publication charge is one drop per byte. */
|
||||
std::uint64_t
|
||||
unrevealedKeyBytes(ReadView const& view, STTx const& tx);
|
||||
|
||||
/** Create an ltPUBLIC_KEY object (ReferenceCount = 1) for every inline PQ
|
||||
key in `tx` that the view does not yet hold. Called by Transactor after
|
||||
the fee is charged, on the tes and the tec path alike. */
|
||||
TER
|
||||
reveal(ApplyView& view, STTx const& tx, beast::Journal j);
|
||||
|
||||
/** If `view` holds a key object for `id`, increment its reference count. */
|
||||
void
|
||||
addReference(ApplyView& view, AccountID const& id, beast::Journal j);
|
||||
|
||||
/** If `view` holds a key object for `id`, decrement its reference count,
|
||||
deleting the object when it reaches zero. */
|
||||
void
|
||||
removeReference(ApplyView& view, AccountID const& id, beast::Journal j);
|
||||
|
||||
/** Net reference adjustment when a set of referencing slots changes from
|
||||
`before` to `after` (e.g. a SignerList replacement): ids only in
|
||||
`before` lose a reference, ids only in `after` gain one. Both vectors
|
||||
may be unsorted and may contain duplicates. */
|
||||
void
|
||||
adjustReferences(
|
||||
ApplyView& view,
|
||||
std::vector<AccountID> before,
|
||||
std::vector<AccountID> after,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace pqkey
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/app/tx/detail/SetAccount.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
@@ -428,12 +429,16 @@ SetAccount::doApply()
|
||||
|
||||
JLOG(j_.trace()) << "Set lsfDisableMaster.";
|
||||
uFlagsOut |= lsfDisableMaster;
|
||||
// featurePQSignatures: an enabled master key references its key
|
||||
// object; disabling the master releases that reference.
|
||||
pqkey::removeReference(view(), account_, j_);
|
||||
}
|
||||
|
||||
if ((uClearFlag == asfDisableMaster) && (uFlagsIn & lsfDisableMaster))
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear lsfDisableMaster.";
|
||||
uFlagsOut &= ~lsfDisableMaster;
|
||||
pqkey::addReference(view(), account_, j_);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -82,9 +83,17 @@ SetRegularKey::doApply()
|
||||
if (!minimumFee(ctx_.app, ctx_.baseFee, view().fees(), view().flags()))
|
||||
sle->setFlag(lsfPasswordSpent);
|
||||
|
||||
// featurePQSignatures: the RegularKey slot is a reference to a
|
||||
// post-quantum key object (if one exists for that AccountID).
|
||||
std::vector<AccountID> const oldKey = sle->isFieldPresent(sfRegularKey)
|
||||
? std::vector<AccountID>{sle->getAccountID(sfRegularKey)}
|
||||
: std::vector<AccountID>{};
|
||||
std::vector<AccountID> newKey;
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfRegularKey))
|
||||
{
|
||||
sle->setAccountID(sfRegularKey, ctx_.tx.getAccountID(sfRegularKey));
|
||||
newKey.push_back(ctx_.tx.getAccountID(sfRegularKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -97,6 +106,7 @@ SetRegularKey::doApply()
|
||||
}
|
||||
|
||||
ctx_.view().update(sle);
|
||||
pqkey::adjustReferences(view(), oldKey, newKey, j_);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,8 @@ SetSignerList::removeSignersFromLedger(
|
||||
Keylet const& accountKeylet,
|
||||
Keylet const& ownerDirKeylet,
|
||||
Keylet const& signerListKeylet,
|
||||
beast::Journal j)
|
||||
beast::Journal j,
|
||||
bool releasePQReferences)
|
||||
{
|
||||
// We have to examine the current SignerList so we know how much to
|
||||
// reduce the OwnerCount.
|
||||
@@ -211,6 +212,11 @@ SetSignerList::removeSignersFromLedger(
|
||||
if (!signers)
|
||||
return tesSUCCESS;
|
||||
|
||||
// featurePQSignatures: each entry held a reference to its key object.
|
||||
if (releasePQReferences)
|
||||
for (auto const& e : signers->getFieldArray(sfSignerEntries))
|
||||
pqkey::removeReference(view, e.getAccountID(sfAccount), j);
|
||||
|
||||
// There are two different ways that the OwnerCount could be managed.
|
||||
// If the lsfOneOwnerCount bit is set then remove just one owner count.
|
||||
// Otherwise use the pre-MultiSignReserve amendment calculation.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define RIPPLE_TX_SETSIGNERLIST_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
@@ -78,7 +79,8 @@ public:
|
||||
Keylet const& accountKeylet,
|
||||
Keylet const& ownerDirKeylet,
|
||||
Keylet const& signerListKeylet,
|
||||
beast::Journal j);
|
||||
beast::Journal j,
|
||||
bool releasePQReferences = true);
|
||||
|
||||
static int
|
||||
signerCountBasedOwnerCountDelta(std::size_t entryCount, Rules const& rules);
|
||||
@@ -132,11 +134,26 @@ public:
|
||||
auto const ownerDirKeylet = keylet::ownerDir(acc);
|
||||
auto const signerListKeylet = keylet::signers(acc);
|
||||
|
||||
// featurePQSignatures: the signer entries are references to
|
||||
// post-quantum key objects. Adjust by the net change, so that
|
||||
// replacing a list with one that keeps a signer does not drop (and
|
||||
// possibly delete) that signer's key object only to re-reference it.
|
||||
std::vector<AccountID> oldSigners;
|
||||
if (auto const old = view.read(signerListKeylet))
|
||||
for (auto const& e : old->getFieldArray(sfSignerEntries))
|
||||
oldSigners.push_back(e.getAccountID(sfAccount));
|
||||
|
||||
// This may be either a create or a replace. Preemptively remove any
|
||||
// old signer list. May reduce the reserve, so this is done before
|
||||
// checking the reserve.
|
||||
if (TER const ter = removeSignersFromLedger(
|
||||
app, view, accountKeylet, ownerDirKeylet, signerListKeylet, j))
|
||||
app,
|
||||
view,
|
||||
accountKeylet,
|
||||
ownerDirKeylet,
|
||||
signerListKeylet,
|
||||
j,
|
||||
/*releasePQReferences=*/false))
|
||||
return ter;
|
||||
|
||||
auto const sle = view.peek(accountKeylet);
|
||||
@@ -186,6 +203,12 @@ public:
|
||||
// If we succeeded, the new entry counts against the
|
||||
// creator's reserve.
|
||||
adjustOwnerCount(view, sle, addedOwnerCount, j);
|
||||
|
||||
std::vector<AccountID> newSigners;
|
||||
for (auto const& e : signers)
|
||||
newSigners.push_back(e.account);
|
||||
pqkey::adjustReferences(
|
||||
view, std::move(oldSigners), std::move(newSigners), j);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/app/tx/detail/SetHook.h>
|
||||
#include <xrpld/app/tx/detail/SignerEntries.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
@@ -37,6 +38,7 @@
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PQPublicKey.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
@@ -441,6 +443,19 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
}
|
||||
}
|
||||
|
||||
// featurePQSignatures: publishing a post-quantum public key to the
|
||||
// ledger costs one drop per byte, charged on the transaction that
|
||||
// first carries the key inline. Like memo bytes it is part of the
|
||||
// accumulator and therefore scales with the load fee.
|
||||
if (view.rules().enabled(featurePQSignatures))
|
||||
{
|
||||
XRPAmount const keyBytes{
|
||||
static_cast<std::int64_t>(pqkey::unrevealedKeyBytes(view, tx))};
|
||||
if (accumulator + keyBytes < accumulator)
|
||||
return XRPAmount{INITIAL_XRP.drops()};
|
||||
accumulator += keyBytes;
|
||||
}
|
||||
|
||||
// RH NOTE: hookExecutionFee = 0, burden = 1 if hooks is not enabled
|
||||
|
||||
// The calculation is (baseFee * burden) + (signerCount * baseFee) +
|
||||
@@ -877,6 +892,13 @@ Transactor::apply()
|
||||
sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
|
||||
|
||||
view().update(sle);
|
||||
|
||||
// featurePQSignatures: the byte fee was just paid, so publish any
|
||||
// inline post-quantum keys now. reset() repeats this on the tec
|
||||
// path so that a claimed fee always buys the key object.
|
||||
result = pqkey::reveal(view(), ctx_.tx, j_);
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
}
|
||||
|
||||
return doApply();
|
||||
@@ -931,12 +953,62 @@ Transactor::checkSign(PreclaimContext const& ctx)
|
||||
return checkSingleSign(ctx);
|
||||
}
|
||||
|
||||
// featurePQSignatures. The AccountID a signing key stands for, in any of
|
||||
// the supported encodings, or std::nullopt if the key is not acceptable.
|
||||
// For a post-quantum key *reference* the ledger object must exist; its
|
||||
// signature is then verified here, against this view, because the
|
||||
// signature check at ingress / preflight could not (see checkValidity).
|
||||
static std::optional<AccountID>
|
||||
signerAccountID(
|
||||
PreclaimContext const& ctx,
|
||||
Blob const& spk,
|
||||
std::function<Expected<void, std::string>(STTx::PQKeyLookup const&)>
|
||||
verifyWithLedger)
|
||||
{
|
||||
auto const key = makeSlice(spk);
|
||||
if (publicKeyType(key))
|
||||
return calcAccountID(PublicKey(key));
|
||||
|
||||
if (!ctx.view.rules().enabled(featurePQSignatures))
|
||||
return std::nullopt;
|
||||
|
||||
if (pq::isInlineKey(key))
|
||||
return pq::calcAccountID(key);
|
||||
|
||||
if (pq::isKeyReference(key))
|
||||
{
|
||||
auto const id = pq::referenceAccountID(key);
|
||||
if (!pqkey::read(ctx.view, id))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "PQ key reference " << toBase58(id) << " not in ledger";
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!(ctx.flags & tapDRY_RUN))
|
||||
{
|
||||
auto const ok = verifyWithLedger(pqkey::makeLookup(ctx.view));
|
||||
if (!ok)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "PQ key reference signature: " << ok.error();
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
Transactor::checkSingleSign(PreclaimContext const& ctx)
|
||||
{
|
||||
// Check that the value in the signing key slot is a public key.
|
||||
auto const pkSigner = ctx.tx.getSigningPubKey();
|
||||
if (!(ctx.flags & tapDRY_RUN) && !publicKeyType(makeSlice(pkSigner)))
|
||||
bool const pqEnabled = ctx.view.rules().enabled(featurePQSignatures);
|
||||
if (!(ctx.flags & tapDRY_RUN) && !publicKeyType(makeSlice(pkSigner)) &&
|
||||
!(pqEnabled &&
|
||||
(pq::isInlineKey(makeSlice(pkSigner)) ||
|
||||
pq::isKeyReference(makeSlice(pkSigner)))))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "checkSingleSign: signing public key type is unknown";
|
||||
@@ -954,9 +1026,25 @@ Transactor::checkSingleSign(PreclaimContext const& ctx)
|
||||
XRPL_ASSERT(
|
||||
(ctx.flags & tapDRY_RUN) || !pkSigner.empty(),
|
||||
"ripple::Transactor::checkSingleSign : non-empty signer or simulation");
|
||||
auto const idSigner = pkSigner.empty()
|
||||
? idAccount
|
||||
: calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
std::optional<AccountID> idSignerOpt;
|
||||
if (pkSigner.empty())
|
||||
idSignerOpt = idAccount;
|
||||
else
|
||||
idSignerOpt = signerAccountID(
|
||||
ctx, pkSigner, [&ctx](STTx::PQKeyLookup const& lookup) {
|
||||
auto const requireCanonicalSig =
|
||||
ctx.view.rules().enabled(featureRequireFullyCanonicalSig)
|
||||
? STTx::RequireFullyCanonicalSig::yes
|
||||
: STTx::RequireFullyCanonicalSig::no;
|
||||
return ctx.tx.checkSign(
|
||||
requireCanonicalSig,
|
||||
ctx.view.rules(),
|
||||
lookup,
|
||||
STTx::PQReferencePolicy::reject);
|
||||
});
|
||||
if (!idSignerOpt)
|
||||
return tefBAD_AUTH;
|
||||
auto const idSigner = *idSignerOpt;
|
||||
bool const isMasterDisabled = sleAccount->isFlag(lsfDisableMaster);
|
||||
|
||||
if (ctx.view.rules().enabled(fixMasterKeyAsRegularKey))
|
||||
@@ -1078,8 +1166,12 @@ Transactor::checkMultiSign(PreclaimContext const& ctx)
|
||||
// need to compute the accountID that is associated with the signer's
|
||||
// public key.
|
||||
auto const spk = txSigner.getFieldVL(sfSigningPubKey);
|
||||
bool const pqEnabled = ctx.view.rules().enabled(featurePQSignatures);
|
||||
|
||||
if (!(ctx.flags & tapDRY_RUN) && !publicKeyType(makeSlice(spk)))
|
||||
if (!(ctx.flags & tapDRY_RUN) && !publicKeyType(makeSlice(spk)) &&
|
||||
!(pqEnabled &&
|
||||
(pq::isInlineKey(makeSlice(spk)) ||
|
||||
pq::isKeyReference(makeSlice(spk)))))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "checkMultiSign: signing public key type is unknown";
|
||||
@@ -1091,9 +1183,29 @@ Transactor::checkMultiSign(PreclaimContext const& ctx)
|
||||
(ctx.flags & tapDRY_RUN) || !spk.empty(),
|
||||
"ripple::Transactor::checkMultiSign : non-empty signer or "
|
||||
"simulation");
|
||||
AccountID const signingAcctIDFromPubKey = spk.empty()
|
||||
? txSignerAcctID
|
||||
: calcAccountID(PublicKey(makeSlice(spk)));
|
||||
std::optional<AccountID> fromPubKey;
|
||||
if (spk.empty())
|
||||
fromPubKey = txSignerAcctID;
|
||||
else
|
||||
fromPubKey = signerAccountID(
|
||||
ctx, spk, [&ctx](STTx::PQKeyLookup const& lookup) {
|
||||
// Verifies every signer; the whole Signers array is
|
||||
// checked once per reference-form signer, which is
|
||||
// cheap next to the verification itself.
|
||||
auto const requireCanonicalSig =
|
||||
ctx.view.rules().enabled(
|
||||
featureRequireFullyCanonicalSig)
|
||||
? STTx::RequireFullyCanonicalSig::yes
|
||||
: STTx::RequireFullyCanonicalSig::no;
|
||||
return ctx.tx.checkSign(
|
||||
requireCanonicalSig,
|
||||
ctx.view.rules(),
|
||||
lookup,
|
||||
STTx::PQReferencePolicy::reject);
|
||||
});
|
||||
if (!fromPubKey)
|
||||
return tefBAD_SIGNATURE;
|
||||
AccountID const signingAcctIDFromPubKey = *fromPubKey;
|
||||
|
||||
// Verify that the signingAcctID and the signingAcctIDFromPubKey
|
||||
// belong together. Here are the rules:
|
||||
@@ -1361,7 +1473,14 @@ Transactor::reset(XRPAmount fee)
|
||||
isTesSuccess(ter), "ripple::Transactor::reset : result is tesSUCCESS");
|
||||
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
view().update(txnAcct);
|
||||
// featurePQSignatures: the view was discarded, so the inline key
|
||||
// objects created in apply() are gone; recreate them, the fee for
|
||||
// them has just been charged again.
|
||||
if (TER const r = pqkey::reveal(view(), ctx_.tx, j_); !isTesSuccess(r))
|
||||
return {r, fee};
|
||||
}
|
||||
|
||||
return {ter, fee};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpld/app/misc/NetworkOPs.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
#include <xrpld/app/tx/detail/PQKeyLedger.h>
|
||||
#include <xrpld/app/tx/detail/SetManifest.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -45,7 +46,8 @@ checkValidity(
|
||||
STTx const& tx,
|
||||
Rules const& rules,
|
||||
Config const& config,
|
||||
ApplyFlags applyFlags)
|
||||
ApplyFlags applyFlags,
|
||||
std::shared_ptr<ReadView const> const& pqKeyLedger)
|
||||
{
|
||||
auto const id = tx.getTransactionID();
|
||||
auto const flags = router.getFlags(id);
|
||||
@@ -111,7 +113,35 @@ checkValidity(
|
||||
// Signature is known bad
|
||||
return {Validity::SigBad, "Transaction has bad signature."};
|
||||
|
||||
if (!(flags & SF_SIGGOOD))
|
||||
// featurePQSignatures: a post-quantum key *reference* can only be
|
||||
// verified against a ledger. With one, verify here (ingress); without
|
||||
// one, leave it to preclaim (Transactor::checkSign), which verifies
|
||||
// against the exact view the transaction is applied to. Either way the
|
||||
// result is never cached: the referenced object can appear or vanish
|
||||
// between ledgers.
|
||||
bool const pqReference =
|
||||
rules.enabled(featurePQSignatures) && tx.usesPQKeyReference();
|
||||
|
||||
if (pqReference)
|
||||
{
|
||||
if (pqKeyLedger)
|
||||
{
|
||||
auto const requireCanonicalSig =
|
||||
rules.enabled(featureRequireFullyCanonicalSig)
|
||||
? STTx::RequireFullyCanonicalSig::yes
|
||||
: STTx::RequireFullyCanonicalSig::no;
|
||||
auto const sigVerify = tx.checkSign(
|
||||
requireCanonicalSig,
|
||||
rules,
|
||||
pqkey::makeLookup(*pqKeyLedger),
|
||||
STTx::PQReferencePolicy::reject);
|
||||
if (!sigVerify)
|
||||
return {Validity::SigBad, sigVerify.error()};
|
||||
}
|
||||
// Deferred (or verified for this ledger only): fall through to the
|
||||
// local checks without touching the signature flags.
|
||||
}
|
||||
else if (!(flags & SF_SIGGOOD))
|
||||
{
|
||||
// Don't know signature state. Check it.
|
||||
auto const requireCanonicalSig =
|
||||
|
||||
@@ -18,21 +18,61 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
// Do nothing
|
||||
#define TRANSACTION(...)
|
||||
#define TRANSACTION_INCLUDE 1
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
#undef TRANSACTION
|
||||
#pragma pop_macro("TRANSACTION")
|
||||
|
||||
// DO NOT INCLUDE TRANSACTOR HEADER FILES HERE.
|
||||
// See the instructions at the top of transactions.macro instead.
|
||||
|
||||
#include <xrpld/app/tx/detail/AMMBid.h>
|
||||
#include <xrpld/app/tx/detail/AMMClawback.h>
|
||||
#include <xrpld/app/tx/detail/AMMCreate.h>
|
||||
#include <xrpld/app/tx/detail/AMMDelete.h>
|
||||
#include <xrpld/app/tx/detail/AMMDeposit.h>
|
||||
#include <xrpld/app/tx/detail/AMMVote.h>
|
||||
#include <xrpld/app/tx/detail/AMMWithdraw.h>
|
||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
||||
#include <xrpld/app/tx/detail/CancelCheck.h>
|
||||
#include <xrpld/app/tx/detail/CancelOffer.h>
|
||||
#include <xrpld/app/tx/detail/CashCheck.h>
|
||||
#include <xrpld/app/tx/detail/Change.h>
|
||||
#include <xrpld/app/tx/detail/ClaimReward.h>
|
||||
#include <xrpld/app/tx/detail/Clawback.h>
|
||||
#include <xrpld/app/tx/detail/CreateCheck.h>
|
||||
#include <xrpld/app/tx/detail/CreateOffer.h>
|
||||
#include <xrpld/app/tx/detail/CreateTicket.h>
|
||||
#include <xrpld/app/tx/detail/Credentials.h>
|
||||
#include <xrpld/app/tx/detail/Cron.h>
|
||||
#include <xrpld/app/tx/detail/CronSet.h>
|
||||
#include <xrpld/app/tx/detail/DID.h>
|
||||
#include <xrpld/app/tx/detail/DeleteAccount.h>
|
||||
#include <xrpld/app/tx/detail/DeleteOracle.h>
|
||||
#include <xrpld/app/tx/detail/DepositPreauth.h>
|
||||
#include <xrpld/app/tx/detail/Escrow.h>
|
||||
#include <xrpld/app/tx/detail/GenesisMint.h>
|
||||
#include <xrpld/app/tx/detail/Import.h>
|
||||
#include <xrpld/app/tx/detail/Invoke.h>
|
||||
#include <xrpld/app/tx/detail/LedgerStateFix.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenAcceptOffer.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenBurn.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenCancelOffer.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenCreateOffer.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenMint.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenModify.h>
|
||||
#include <xrpld/app/tx/detail/PayChan.h>
|
||||
#include <xrpld/app/tx/detail/Payment.h>
|
||||
#include <xrpld/app/tx/detail/PermissionedDomainDelete.h>
|
||||
#include <xrpld/app/tx/detail/PermissionedDomainSet.h>
|
||||
#include <xrpld/app/tx/detail/Remit.h>
|
||||
#include <xrpld/app/tx/detail/SetAccount.h>
|
||||
#include <xrpld/app/tx/detail/SetHook.h>
|
||||
#include <xrpld/app/tx/detail/SetManifest.h>
|
||||
#include <xrpld/app/tx/detail/SetOracle.h>
|
||||
#include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#include <xrpld/app/tx/detail/SetRemarks.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#include <xrpld/app/tx/detail/SetTrust.h>
|
||||
#include <xrpld/app/tx/detail/URIToken.h>
|
||||
#include <xrpld/app/tx/detail/XChainBridge.h>
|
||||
#include <xrpld/app/tx/detail/XahauGenesis.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -102,9 +102,6 @@ struct ConfigSection
|
||||
#define SECTION_VALIDATORS "validators"
|
||||
#define SECTION_VALIDATOR_TOKEN "validator_token"
|
||||
#define SECTION_VETO_AMENDMENTS "veto_amendments"
|
||||
// Testnet-only: amendments enabled on-ledger that this node should not treat
|
||||
// as unsupported (i.e. should not become amendment blocked over).
|
||||
#define SECTION_IGNORE_AMENDMENTS "ignore_amendments"
|
||||
#define SECTION_WORKERS "workers"
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -1563,7 +1563,9 @@ checkXUSHTransaction(
|
||||
app.getHashRouter(),
|
||||
*stx,
|
||||
app.getLedgerMaster().getValidatedRules(),
|
||||
app.config());
|
||||
app.config(),
|
||||
tapNONE,
|
||||
app.openLedger().current());
|
||||
validity != Validity::Valid)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <xrpld/app/ledger/InboundLedgers.h>
|
||||
#include <xrpld/app/ledger/InboundTransactions.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/ledger/TransactionMaster.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/misc/LoadFeeTrack.h>
|
||||
@@ -2995,7 +2996,9 @@ PeerImp::checkTransaction(
|
||||
app_.getHashRouter(),
|
||||
*stx,
|
||||
app_.getLedgerMaster().getValidatedRules(),
|
||||
app_.config());
|
||||
app_.config(),
|
||||
tapNONE,
|
||||
app_.openLedger().current());
|
||||
valid != Validity::Valid)
|
||||
{
|
||||
if (!validReason.empty())
|
||||
|
||||
@@ -632,7 +632,12 @@ transactionConstructImpl(
|
||||
sttxNew->getTransactionID(),
|
||||
Validity::SigGoodOnly);
|
||||
if (checkValidity(
|
||||
app.getHashRouter(), *sttxNew, rules, app.config())
|
||||
app.getHashRouter(),
|
||||
*sttxNew,
|
||||
rules,
|
||||
app.config(),
|
||||
tapNONE,
|
||||
app.openLedger().current())
|
||||
.first != Validity::Valid)
|
||||
{
|
||||
ret.first = RPC::make_error(rpcINTERNAL, "Invalid signature.");
|
||||
|
||||
@@ -683,6 +683,20 @@ parseDID(Json::Value const& params, Json::Value& jvResult)
|
||||
return keylet::did(*account).key;
|
||||
}
|
||||
|
||||
// featurePQSignatures: "pq_public_key": "<r-address of the key>"
|
||||
static std::optional<uint256>
|
||||
parsePQPublicKey(Json::Value const& params, Json::Value& jvResult)
|
||||
{
|
||||
auto const account = parseBase58<AccountID>(params.asString());
|
||||
if (!account || account->isZero())
|
||||
{
|
||||
jvResult[jss::error] = "malformedAddress";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return keylet::pqPublicKey(*account).key;
|
||||
}
|
||||
|
||||
static std::optional<uint256>
|
||||
parseOracle(Json::Value const& params, Json::Value& jvResult)
|
||||
{
|
||||
@@ -1153,6 +1167,7 @@ doLedgerEntry(RPC::JsonContext& context)
|
||||
{jss::permissioned_domain,
|
||||
parsePermissionedDomains,
|
||||
ltPERMISSIONED_DOMAIN},
|
||||
{jss::pq_public_key, parsePQPublicKey, ltPUBLIC_KEY},
|
||||
{jss::random, parseRandom, ltRANDOM},
|
||||
{jss::ripple_state, parseRippleState, ltRIPPLE_STATE},
|
||||
// This is an alias, since the `ledger_data` filter uses jss::state
|
||||
|
||||
@@ -218,7 +218,9 @@ doSubmit(RPC::JsonContext& context)
|
||||
context.app.getHashRouter(),
|
||||
*stTx,
|
||||
context.ledgerMaster.getCurrentLedger()->rules(),
|
||||
context.app.config());
|
||||
context.app.config(),
|
||||
tapNONE,
|
||||
context.app.openLedger().current());
|
||||
if (validity != Validity::Valid)
|
||||
{
|
||||
jvResult[jss::error] = "invalidTransaction";
|
||||
|
||||
Reference in new issue
Block a user