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:

  1. Logging: Enable trace logging for crypto operations

  2. Standalone mode: Test without network complexity

  3. GDB: Step through code, inspect variables

  4. Valgrind: Detect memory issues

  5. Unit tests: Verify correctness

  6. Benchmarking: Measure performance

  7. Inspection tools: Examine keys, signatures, addresses

  8. 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