Debugging & Development Tools
Introduction
This appendix provides practical tools and techniques for debugging cryptographic code in rippled, testing implementations, and developing new cryptographic features.
Logging Cryptographic Operations
Enable Debug Logging
# Edit rippled.cfg
[rpc_startup]
{ "command": "log_level", "severity": "trace" }
# Or via RPC
./rippled log_level partition=Transaction severity=trace
Monitor Signature Verification
# Watch for signature verification
./rippled --conf rippled.cfg 2>&1 | grep -i "verify\|sign\|signature"
# Watch for failures
./rippled --conf rippled.cfg 2>&1 | grep -i "tefBAD_SIGNATURE\|temINVALID"
Custom Logging
// Add debug logging to crypto code
#include <ripple/beast/core/Journal.h>
void debugSign(PublicKey const& pk, SecretKey const& sk, Slice const& m)
{
JLOG(journal.trace()) << "Signing with key type: "
<< (publicKeyType(pk) == KeyType::ed25519 ? "ed25519" : "secp256k1");
auto sig = sign(pk, sk, m);
JLOG(journal.trace()) << "Signature size: " << sig.size();
JLOG(journal.trace()) << "Signature (hex): " << strHex(sig);
}
Standalone Mode for Testing
Start Standalone Node
# Start rippled in standalone mode (no network)
./rippled --standalone --conf rippled.cfg
Generate Test Accounts
# Generate new account with ed25519
./rippled wallet_propose ed25519
# Output:
# {
# "account_id": "rN7n7otQDd6FczFgLdlqtyMVrn3LNU8B4C",
# "key_type": "ed25519",
# "master_key": "SNIT ARMY BOOM CALF ABLE ATOM CURE BARN FOWL ASIA HEAT TOUR",
# "master_seed": "sn3nxiW7v8KXzPzAqzyHXbSSKNuN9",
# "master_seed_hex": "DEDCE9CE67B451D852FD4E846FCDE31C",
# "public_key": "aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3",
# "public_key_hex": "ED9434799226374926EDA3B54B1B461B4ABF7237962EEB1144C10A7CA6A9D32C64"
# }
# Generate with secp256k1
./rippled wallet_propose secp256k1
Test Transactions
# Submit test transaction
./rippled submit '{
"tx_json": {
"Account": "rN7n7otQDd6FczFgLdlqtyMVrn3LNU8B4C",
"TransactionType": "Payment",
"Destination": "rLHzPsX6oXkzU9w7fvQqJvGjzVtL5oJ47R",
"Amount": "1000000"
},
"secret": "sn3nxiW7v8KXzPzAqzyHXbSSKNuN9",
"key_type": "ed25519"
}'
# Manually close ledger
./rippled ledger_accept
Debugging with GDB
Compile with Debug Symbols
# Build with debug info
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
Basic GDB Commands
# Start rippled in gdb
gdb --args ./rippled --standalone --conf rippled.cfg
# Common commands:
(gdb) break SecretKey.cpp:randomSecretKey # Set breakpoint
(gdb) run # Run program
(gdb) next # Step over
(gdb) step # Step into
(gdb) continue # Continue execution
(gdb) print sk # Print variable
(gdb) backtrace # Show call stack
Inspect Cryptographic Data
# Examine secret key bytes
(gdb) x/32xb &secretKey # Display 32 bytes in hex
# Examine signature
(gdb) x/64xb signature.data()
# Print public key
(gdb) print /x publicKey
# Check key type
(gdb) print publicKeyType(pk)
Conditional Breakpoints
# Break only for ed25519 keys
(gdb) break sign if publicKeyType(pk) == KeyType::ed25519
# Break on signature verification failure
(gdb) break verify if $retval == false
Memory Debugging with Valgrind
Check for Memory Leaks
# Run with valgrind
valgrind --leak-check=full --show-leak-kinds=all ./rippled --standalone
# Look for:
# - Definitely lost: Memory leaks
# - Possibly lost: Potential leaks
# - Still reachable: OK (cleanup at exit)
Check for Uninitialized Memory
# Detect uninitialized reads
valgrind --track-origins=yes ./rippled --standalone
# Look for:
# "Conditional jump or move depends on uninitialised value(s)"
# "Use of uninitialised value of size X"
Unit Testing
Run Crypto Tests
# Run all tests
./rippled --unittest
# Run specific test suite
./rippled --unittest=ripple.protocol.SecretKey
# Run with specific algorithm
./rippled --unittest=ripple.protocol.SecretKey:Ed25519
Write Custom Tests
// From src/test/protocol/SecretKey_test.cpp
class SecretKey_test : public beast::unit_test::suite
{
public:
void testRandomGeneration()
{
// Generate keys
auto sk1 = randomSecretKey();
auto sk2 = randomSecretKey();
// Should be different
expect(sk1 != sk2, "Random keys should be unique");
// Should be correct size
expect(sk1.size() == 32, "Secret key should be 32 bytes");
}
void testSigning()
{
auto [pk, sk] = randomKeyPair(KeyType::ed25519);
std::vector<uint8_t> message{0x01, 0x02, 0x03};
auto sig = sign(pk, sk, makeSlice(message));
// Verify signature
bool valid = verify(pk, makeSlice(message), sig, true);
expect(valid, "Signature should verify");
// Modify message
message[0] = 0xFF;
valid = verify(pk, makeSlice(message), sig, true);
expect(!valid, "Modified message should not verify");
}
void run() override
{
testRandomGeneration();
testSigning();
}
};
BEAST_DEFINE_TESTSUITE(SecretKey, protocol, ripple);
Benchmarking
Measure Performance
#include <chrono>
void benchmarkSigning()
{
auto [pk, sk] = randomKeyPair(KeyType::ed25519);
std::vector<uint8_t> message(1000, 0xAA);
constexpr int iterations = 1000;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
auto sig = sign(pk, sk, makeSlice(message));
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "Average signing time: "
<< (duration.count() / static_cast<double>(iterations))
<< " μs\n";
}
Compare Algorithms
void compareAlgorithms()
{
std::cout << "=== Signing Performance ===\n";
// Ed25519
{
auto [pk, sk] = randomKeyPair(KeyType::ed25519);
auto time = measureSign(pk, sk, 1000);
std::cout << "Ed25519: " << time << " μs/op\n";
}
// secp256k1
{
auto [pk, sk] = randomKeyPair(KeyType::secp256k1);
auto time = measureSign(pk, sk, 1000);
std::cout << "secp256k1: " << time << " μs/op\n";
}
}
Inspecting Key Material
View Public Key Details
void inspectPublicKey(PublicKey const& pk)
{
std::cout << "=== Public Key Analysis ===\n";
auto type = publicKeyType(pk);
std::cout << "Type: ";
if (!type) {
std::cout << "INVALID\n";
return;
}
if (*type == KeyType::secp256k1)
std::cout << "secp256k1 (ECDSA)\n";
else if (*type == KeyType::ed25519)
std::cout << "ed25519 (EdDSA)\n";
std::cout << "Size: " << pk.size() << " bytes\n";
std::cout << "Hex: " << strHex(pk) << "\n";
auto accountID = calcAccountID(pk);
std::cout << "Account ID (hex): " << strHex(accountID) << "\n";
std::cout << "Address: " << toBase58(accountID) << "\n";
}
Verify Key Pair Consistency
bool verifyKeyPair(PublicKey const& pk, SecretKey const& sk)
{
// Derive public key from secret
auto derived = derivePublicKey(publicKeyType(pk).value(), sk);
if (derived != pk) {
std::cout << "ERROR: Public key doesn't match secret key!\n";
std::cout << "Expected: " << strHex(pk) << "\n";
std::cout << "Derived: " << strHex(derived) << "\n";
return false;
}
std::cout << "✓ Key pair is consistent\n";
return true;
}
Testing Signature Canonicality
Check secp256k1 Canonicality
void testCanonicality(Slice const& signature)
{
auto canon = ecdsaCanonicality(signature);
if (!canon) {
std::cout << "ERROR: Invalid signature format\n";
return;
}
switch (*canon) {
case ECDSACanonicality::fullyCanonical:
std::cout << "✓ Fully canonical (S ≤ order/2)\n";
break;
case ECDSACanonicality::canonical:
std::cout << "⚠ Canonical but not fully (S > order/2)\n";
std::cout << " Should normalize for malleability prevention\n";
break;
}
}
Hex Dump Utility
void hexDump(void const* data, size_t size, std::string const& label = "")
{
if (!label.empty())
std::cout << label << ":\n";
auto const* bytes = static_cast<uint8_t const*>(data);
for (size_t i = 0; i < size; ++i) {
if (i % 16 == 0)
std::cout << std::hex << std::setw(4) << std::setfill('0') << i << ": ";
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(bytes[i]) << " ";
if ((i + 1) % 16 == 0 || i + 1 == size)
std::cout << "\n";
}
std::cout << std::dec; // Reset to decimal
}
// Usage:
hexDump(signature.data(), signature.size(), "Signature");
Address Sanitizer
Compile with AddressSanitizer
# Build with ASan
cmake -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
..
make
# Run
./rippled --standalone
Detect Issues
Use-after-free
Heap buffer overflow
Stack buffer overflow
Memory leaks
Use of uninitialized memory
Common Debug Scenarios
Debug Signature Verification Failure
void debugVerificationFailure(
PublicKey const& pk,
Slice const& message,
Slice const& signature)
{
std::cout << "=== Debugging Signature Verification ===\n";
// Check public key
auto pkType = publicKeyType(pk);
if (!pkType) {
std::cout << "ERROR: Invalid public key format\n";
return;
}
std::cout << "✓ Public key type: "
<< (*pkType == KeyType::ed25519 ? "ed25519" : "secp256k1")
<< "\n";
// Check signature size
if (*pkType == KeyType::ed25519 && signature.size() != 64) {
std::cout << "ERROR: Ed25519 signature should be 64 bytes, got "
<< signature.size() << "\n";
return;
}
std::cout << "✓ Signature size: " << signature.size() << " bytes\n";
// Check canonicality
if (*pkType == KeyType::secp256k1) {
auto canon = ecdsaCanonicality(signature);
if (!canon) {
std::cout << "ERROR: Invalid DER encoding\n";
return;
}
if (*canon != ECDSACanonicality::fullyCanonical) {
std::cout << "WARNING: Signature not fully canonical\n";
}
}
// Try verification
bool valid = verify(pk, message, signature, true);
std::cout << "Verification result: " << (valid ? "✓ VALID" : "✗ INVALID") << "\n";
if (!valid) {
std::cout << "\nPossible causes:\n";
std::cout << "- Wrong public key\n";
std::cout << "- Wrong message\n";
std::cout << "- Corrupted signature\n";
std::cout << "- Algorithm mismatch\n";
}
}
Summary
Essential debugging tools and techniques:
Logging: Enable trace logging for crypto operations
Standalone mode: Test without network complexity
GDB: Step through code, inspect variables
Valgrind: Detect memory issues
Unit tests: Verify correctness
Benchmarking: Measure performance
Inspection tools: Examine keys, signatures, addresses
Sanitizers: Catch memory errors automatically
Best practices:
Test with both ed25519 and secp256k1
Verify canonicality for secp256k1
Check key pair consistency
Use hex dumps for visual inspection
Always check error returns
Last updated