go-blockchain/docs/cryptography.md
Snider f7cd812263
Some checks failed
Security Scan / security (push) Successful in 7s
Test / Test (push) Failing after 18s
docs: add human-friendly documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:02:39 +00:00

9.2 KiB

title description
Cryptography Cryptographic primitives provided by the CGo bridge to libcryptonote.

Cryptography

All cryptographic operations are provided by the crypto/ package, which bridges Go to the vendored C++ CryptoNote library via CGo. This is the only package in the codebase that crosses the CGo boundary.

Build Requirements

The C++ library must be built before running tests that require crypto:

cmake -S crypto -B crypto/build -DCMAKE_BUILD_TYPE=Release
cmake --build crypto/build --parallel

This produces libcryptonote.a (~680KB) and librandomx.a. CGo links against both.

Packages that do not require crypto (config/, types/, wire/, difficulty/) remain buildable without a C toolchain.

Hash Functions

Keccak-256 (FastHash)

The primary hash function throughout the protocol. Uses the original Keccak-256 (pre-NIST), not SHA3-256. The two differ in padding and produce different outputs.

hash := crypto.FastHash(data)  // returns [32]byte

Used for: transaction hashes, block hashes, key derivations, address checksums, and proof construction.

Tree Hash

Merkle tree hash over a set of transaction hashes within a block. Uses Keccak-256 as the leaf/node hash. Implemented in wire/treehash.go as a direct port of the C++ crypto/tree-hash.c.

RandomX (PoW)

Proof-of-Work hash function. The vendored RandomX source (26 files including x86_64 JIT compiler) is built as a separate static library.

hash := crypto.RandomXHash(key, input)  // key = "LetheanRandomXv1"

Input format: header_hash(32 bytes) || nonce(8 bytes LE).

Curve Operations

All public keys, key images, commitments, and signatures are built on the Ed25519 curve (Twisted Edwards form of Curve25519), providing 128-bit security.

Key Types

Type Size Description
PublicKey 32 bytes Compressed Ed25519 point
SecretKey 32 bytes Scalar modulo group order l
KeyImage 32 bytes Double-spend detection tag
KeyDerivation 32 bytes Diffie-Hellman shared secret point

Key Generation

pub, sec, err := crypto.GenerateKeys()          // Random key pair
pub, err := crypto.SecretToPublic(sec)           // Derive public from secret
valid := crypto.CheckKey(pub)                    // Validate curve point

Scalar Reduction

crypto.ScReduce32(&key)  // Reduce 32-byte value modulo group order l

Required when converting a hash output to a valid secret key scalar. Used in wallet view key derivation: viewSecret = sc_reduce32(Keccak256(spendSecret)).

Stealth Addresses

CryptoNote stealth addresses ensure every output appears at a unique one-time address that only the recipient can identify and spend.

Key Derivation

// Sender: compute shared secret from recipient's view public key and tx secret key
derivation, err := crypto.GenerateKeyDerivation(viewPubKey, txSecretKey)

// Derive per-output one-time public key
outputKey, err := crypto.DerivePublicKey(derivation, outputIndex, spendPubKey)

Output Scanning (Recipient)

// Compute shared secret from tx public key and own view secret key
derivation, err := crypto.GenerateKeyDerivation(txPubKey, viewSecretKey)

// Derive expected output public key
expected, err := crypto.DerivePublicKey(derivation, outputIndex, spendPubKey)

// If expected == actual output key, this output belongs to us
// Then derive the spending secret key:
outputSecKey, err := crypto.DeriveSecretKey(derivation, outputIndex, spendSecretKey)

Key Images

Each output can only be spent once. When spent, its key image is revealed and recorded on chain. Any subsequent attempt to spend the same output produces an identical key image, which the network rejects.

keyImage, err := crypto.GenerateKeyImage(outputPubKey, outputSecKey)
valid := crypto.ValidateKeyImage(keyImage)

Key images are deterministic (same output always produces the same image) and unlinkable (the image cannot be traced back to the output without the secret key).

Signatures

Standard Signatures

Non-ring Ed25519 signatures for general-purpose message signing:

sig, err := crypto.GenerateSignature(messageHash, pubKey, secKey)
valid := crypto.CheckSignature(messageHash, pubKey, sig)

NLSAG Ring Signatures (Pre-HF4)

Classic CryptoNote ring signatures. The spender proves ownership of one output within a ring of decoys without revealing which output is theirs.

Ring size: 10 decoys + 1 real (pre-HF4, configurable via config.DefaultDecoySetSize).

// Generate a ring signature
sigs, err := crypto.GenerateRingSignature(
    txHash,         // Message being signed
    keyImage,       // Key image of the real input
    ringPubKeys,    // Public keys of all ring members ([][32]byte)
    realSecretKey,  // Secret key of the actual signer
    realIndex,      // Position of the real key in the ring
)

// Verify a ring signature
valid := crypto.CheckRingSignature(txHash, keyImage, ringPubKeys, sigs)

CLSAG Ring Signatures (Post-HF4)

Compact Linkable Spontaneous Anonymous Group signatures, introduced with HF4 (Zarcanum). Smaller and faster than NLSAG.

Ring size: 15 decoys + 1 real (from HF4, config.HF4MandatoryDecoySetSize).

Three variants with increasing complexity:

Variant Base Points Used For
CLSAG-GG G, G Efficient ring signatures
CLSAG-GGX G, G, X Confidential transaction signatures
CLSAG-GGXXG G, G, X, X, G PoS stake signatures
// Generate CLSAG-GG
sig, err := crypto.GenerateCLSAGGG(hash, ring, ringSize,
    pseudoOut, keyImage, secretX, secretF, secretIndex)

// Verify CLSAG-GG
valid := crypto.VerifyCLSAGGG(hash, ring, ringSize, pseudoOut, keyImage, sig)

// Verify CLSAG-GGX (confidential transactions)
valid := crypto.VerifyCLSAGGGX(hash, ring, ringSize,
    pseudoOutCommitment, pseudoOutAssetID, keyImage, sig)

// Verify CLSAG-GGXXG (PoS staking)
valid := crypto.VerifyCLSAGGGXXG(hash, ring, ringSize,
    pseudoOutCommitment, pseudoOutAssetID, extendedCommitment, keyImage, sig)

Ring buffer convention: Ring entries are flat byte arrays. Each entry packs 32-byte public keys per dimension:

  • GG: 64 bytes per entry (stealth_addr + amount_commitment)
  • GGX: 96 bytes per entry (+ blinded_asset_id)
  • GGXXG: 128 bytes per entry (+ concealing_point)

Cofactor Handling (1/8 Premultiplication)

On-chain curve points (commitments, blinded asset IDs) are stored premultiplied by the cofactor inverse (1/8). This is a Zarcanum convention for efficient batch verification.

// Convert full point to on-chain form
onChain, err := crypto.PointDiv8(fullPoint)

// Convert on-chain form back to full point
full, err := crypto.PointMul8(onChainPoint)

// Point subtraction
result, err := crypto.PointSub(a, b)

CLSAG generate takes full points; CLSAG verify takes premultiplied (on-chain) values.

Range Proofs

Bulletproofs+ (BP+)

Zero-knowledge range proofs demonstrating that a committed value lies within [0, 2^64) without revealing the value. Used for transaction output amounts from HF4.

valid := crypto.VerifyBPP(proofBlob, commitments)  // commitments premultiplied by 1/8

Bulletproofs++ Enhanced (BPPE)

Extended variant used in Zarcanum PoS proofs. Proves the stake amount meets the minimum threshold without revealing the balance.

valid := crypto.VerifyBPPE(proofBlob, commitments)

Asset Surjection Proofs (BGE)

Bootle-Groth-Esgin one-out-of-many proofs. Each output proves its blinded asset ID corresponds to one of the input asset IDs, without revealing which. Introduced in HF5 (confidential assets).

valid := crypto.VerifyBGE(contextHash, ring, proofBlob)

Zarcanum PoS Proofs

Composite proof for Proof-of-Stake block production. Combines stake commitment proof, Schnorr response scalars, BPPE range proof, and CLSAG-GGXXG ring signature.

valid := crypto.VerifyZarcanum(hash, proofBlob)

Pedersen Commitments

Commits to a value v with blinding factor r as C = v * H + r * G. Additively homomorphic: C(a) + C(b) = C(a+b), allowing verification that inputs and outputs balance without revealing amounts.

The native coin asset ID H is a fixed curve point. From HF4, all transaction amounts use Pedersen commitments.

Wallet Encryption

Wallet files are encrypted with ChaCha8 (8-round ChaCha stream cipher). The encryption key is derived from the user's password via Argon2id (time=3, memory=64MB, threads=4) with AES-256-GCM.

Summary

Primitive Purpose Available From
Keccak-256 Primary hash Genesis
Tree hash Tx Merkle root Genesis
RandomX PoW hash Genesis
Ed25519 ops Key arithmetic Genesis
Key derivation Stealth addresses Genesis
Key images Double-spend prevention Genesis
NLSAG ring sigs Input privacy (classic) Genesis
CLSAG-GG Efficient ring sigs HF4
CLSAG-GGX Confidential tx sigs HF4
CLSAG-GGXXG PoS stake sigs HF4
Bulletproofs+ Amount range proofs HF4
Bulletproofs++ Stake range proofs HF4
Zarcanum proofs PoS composite proofs HF4
BGE surjection Asset type proofs HF5
Pedersen commitments Hidden amounts HF4