# Architecture go-blockchain is a pure Go implementation of the Lethean blockchain protocol. It provides chain configuration, core cryptographic data types, consensus-critical wire serialisation, and difficulty adjustment for the Lethean CryptoNote/Zano-fork chain. Module path: `forge.lthn.ai/core/go-blockchain` --- ## Package Structure ``` config/ Chain parameters (mainnet/testnet), hardfork schedule types/ Core data types: Hash, PublicKey, Address, Block, Transaction wire/ Binary serialisation (CryptoNote varint encoding) difficulty/ PoW + PoS difficulty adjustment (LWMA variant) crypto/ CGo bridge to vendored C++ libcryptonote (keys, signatures, proofs) p2p/ CryptoNote P2P command types (handshake, sync, relay) ``` ### config/ Defines every consensus-critical constant for the Lethean chain, derived directly from the canonical C++ source files `currency_config.h.in` and `default.cmake`. Constants cover tokenomics, address prefixes, network ports, difficulty parameters, block and transaction limits, version numbers, PoS parameters, P2P constants, network identity, currency identity, and alias rules. The `ChainConfig` struct aggregates all parameters into a single value. Pre-populated `Mainnet` and `Testnet` variables are provided as package-level globals. The hardfork schedule is defined in `hardfork.go` with lookup functions for querying active versions at any block height. ### types/ Fixed-size byte array types matching the CryptoNote specification: - `Hash` (32 bytes) -- Keccak-256 hash values - `PublicKey` (32 bytes) -- Ed25519 public keys - `SecretKey` (32 bytes) -- Ed25519 secret keys - `KeyImage` (32 bytes) -- double-spend detection images - `Signature` (64 bytes) -- cryptographic signatures Also contains the full address encoding/decoding implementation (CryptoNote base58 with Keccak-256 checksums), block header and block structures, and all transaction types across versions 0 through 3. ### wire/ Consensus-critical binary serialisation for blocks, transactions, and all wire primitives. All encoding is bit-identical to the C++ reference implementation. **Primitives:** - `Encoder` / `Decoder` -- sticky-error streaming codec (call `Err()` once) - `EncodeVarint` / `DecodeVarint` -- 7-bit LEB128 with MSB continuation - `Keccak256` -- pre-NIST Keccak-256 (CryptoNote's `cn_fast_hash`) **Block serialisation:** - `EncodeBlockHeader` / `DecodeBlockHeader` -- wire order: major, nonce(LE64), prev_id, minor(varint), timestamp(varint), flags - `EncodeBlock` / `DecodeBlock` -- header + miner_tx + tx_hashes - `BlockHashingBlob` -- serialised header || tree_root || varint(tx_count) - `BlockHash` -- Keccak-256 of varint(len) + block hashing blob **Transaction serialisation (v0/v1):** - `EncodeTransactionPrefix` / `DecodeTransactionPrefix` -- version-dependent field ordering (v0/v1: version, vin, vout, extra; v2+: version, vin, extra, vout) - `EncodeTransaction` / `DecodeTransaction` -- prefix + signatures + attachment - All variant tags match `SET_VARIANT_TAGS` from `currency_basic.h` - Extra/attachment stored as raw wire bytes for bit-identical round-tripping **Hashing:** - `TreeHash` -- CryptoNote Merkle tree (direct port of `crypto/tree-hash.c`) - `TransactionPrefixHash` -- Keccak-256 of serialised prefix - `TransactionHash` -- Keccak-256 of full serialised transaction ### difficulty/ LWMA (Linear Weighted Moving Average) difficulty adjustment algorithm for both PoW and PoS blocks. Examines a window of recent block timestamps and cumulative difficulties to calculate the next target difficulty. ### p2p/ CryptoNote P2P protocol command types for peer-to-peer communication. This package provides encode/decode for all Levin protocol commands, built on the `node/levin/` sub-package in go-p2p. The package depends on `forge.lthn.ai/core/go-p2p/node/levin` for the Levin wire format (33-byte header, portable storage serialisation, framed TCP connections) and defines the application-level command semantics: - **handshake.go** -- COMMAND_HANDSHAKE (1001): NodeData (network ID, peer ID, local time, port) + CoreSyncData exchange. Peerlist decoding from packed 24-byte entries. - **timedsync.go** -- COMMAND_TIMED_SYNC (1002): periodic blockchain state sync. - **ping.go** -- COMMAND_PING (1003): simple liveness check. - **relay.go** -- Block relay (2001), transaction relay (2002), chain request/response (2006/2007). - **sync.go** -- CoreSyncData type (current_height, top_id, checkpoint, core_time, client_version, pruning mode). - **commands.go** -- Command ID re-exports from the levin package. - **integration_test.go** -- Build-tagged (`//go:build integration`) test that TCP-connects to the C++ testnet daemon on localhost:46942 and performs a full handshake + ping exchange. The Levin wire format in go-p2p includes: - **node/levin/header.go** -- 33-byte packed header with signature validation. - **node/levin/varint.go** -- Portable storage varint (2-bit size mark, NOT the same as CryptoNote LEB128 varints in wire/). - **node/levin/storage.go** -- Portable storage section encode/decode (epee KV format with 12 type tags). - **node/levin/connection.go** -- Framed TCP connection with header + payload read/write. --- ## Key Types ### ChainConfig ```go type ChainConfig struct { Name string Abbreviation string IsTestnet bool CurrencyFormationVersion uint64 Coin uint64 DisplayDecimalPoint uint8 BlockReward uint64 DefaultFee uint64 MinimumFee uint64 Premine uint64 AddressPrefix uint64 IntegratedAddressPrefix uint64 AuditableAddressPrefix uint64 AuditableIntegratedAddressPrefix uint64 P2PPort uint16 RPCPort uint16 StratumPort uint16 DifficultyPowTarget uint64 DifficultyPosTarget uint64 DifficultyWindow uint64 DifficultyLag uint64 DifficultyCut uint64 DifficultyPowStarter uint64 DifficultyPosStarter uint64 MaxBlockNumber uint64 TxMaxAllowedInputs uint64 TxMaxAllowedOutputs uint64 DefaultDecoySetSize uint64 HF4MandatoryDecoySetSize uint64 MinedMoneyUnlockWindow uint64 P2PMaintainersPubKey string } ``` Pre-populated globals `Mainnet` and `Testnet` contain the complete parameter sets for each network. Mainnet uses ports 36940-36942; testnet uses 46940-46942. ### HardFork ```go type HardFork struct { Version uint8 Height uint64 Mandatory bool Description string } ``` Seven hardfork versions are defined (HF0 through HF6). On mainnet, HF0 is active from genesis, HF1 and HF2 activate after block 10,080, and HF3 through HF6 are scheduled at height 999,999,999 (effectively future). On testnet, most forks activate early for testing. ### Address ```go type Address struct { SpendPublicKey PublicKey ViewPublicKey PublicKey Flags uint8 } ``` Four address types are supported via distinct prefixes: | Type | Prefix | Leading chars | |------|--------|---------------| | Standard | `0x1eaf7` | `iTHN` | | Integrated | `0xdeaf7` | `iTHn` | | Auditable | `0x3ceff7` | `iThN` | | Auditable integrated | `0x8b077` | `iThn` | ### Block and Transaction ```go type BlockHeader struct { MajorVersion uint8 Nonce uint64 PrevID Hash MinorVersion uint64 // varint on wire Timestamp uint64 // varint on wire Flags uint8 } type Block struct { BlockHeader MinerTx Transaction TxHashes []Hash } type Transaction struct { Version uint64 // varint on wire Vin []TxInput Vout []TxOutput Extra []byte // raw wire bytes (variant vector) Signatures [][]Signature // v0/v1 only Attachment []byte // raw wire bytes (variant vector) Proofs []byte // raw wire bytes (v2+ only) HardforkID uint8 // v3+ only } ``` Transaction versions progress through the hardfork schedule: | Version | Era | Description | |---------|-----|-------------| | 0 | Genesis | Coinbase transactions | | 1 | Pre-HF4 | Standard transparent transactions | | 2 | Post-HF4 | Zarcanum confidential transactions (CLSAG) | | 3 | Post-HF5 | Confidential assets with surjection proofs | Input types: `TxInputGenesis` (coinbase, tag `0x00`) and `TxInputToKey` (standard spend with ring signature, tag `0x01`). Output types: `TxOutputBare` (transparent, tag `0x24`) and `TxOutputZarcanum` (confidential with Pedersen commitments, tag `0x26`). Additional types: `TxOutToKey` (public key + mix_attr, 33 bytes on wire), `TxOutRef` (variant: global index or ref_by_id). --- ## Design Decisions ### Why CryptoNote Base58 CryptoNote uses its own base58 variant (not Bitcoin's base58check). The alphabet omits `0`, `O`, `I`, and `l` to avoid visual ambiguity. Data is split into 8-byte blocks, each encoded independently into 11 base58 characters. The final partial block produces fewer characters according to a fixed mapping table (`base58BlockSizes`). This block-based approach differs from Bitcoin's whole-number division and produces different output for the same input bytes. The implementation uses `math/big` for block conversion rather than a lookup table. This is correct for all uint64 ranges but is not optimised for high-throughput address generation. Performance optimisation is deferred to a later phase if profiling identifies it as a bottleneck. ### Why Keccak-256 (Not SHA3-256) CryptoNote predates the NIST SHA-3 standard. It uses the original Keccak-256 submission (`sha3.NewLegacyKeccak256()`), which differs from the finalised SHA3-256 in padding. This is consensus-critical -- using SHA3-256 would produce different checksums and break address compatibility with the C++ node. ### Address Encoding Algorithm 1. Encode the address prefix as a CryptoNote varint 2. Append the 32-byte spend public key 3. Append the 32-byte view public key 4. Append the 1-byte flags field 5. Compute Keccak-256 over bytes 1-4, take the first 4 bytes as checksum 6. Append the 4-byte checksum 7. Encode the entire blob using CryptoNote base58 Decoding reverses this process: base58 decode, extract and validate the varint prefix, verify the Keccak-256 checksum, then extract the two keys and flags. ### Block Hash Length Prefix The C++ code computes block hashes via `get_object_hash(get_block_hashing_blob(b))`. Because `get_block_hashing_blob` returns a `blobdata` (std::string) and `get_object_hash` serialises its argument through `binary_archive` before hashing, the actual hash input is `varint(len(blob)) || blob` -- the binary archive prepends a varint length when serialising a string. This CryptoNote convention is replicated in Go's `BlockHash` function. ### Extra as Raw Bytes Transaction extra, attachment, and proofs fields are stored as opaque raw wire bytes rather than being fully parsed into Go structures. The `decodeRawVariantVector` function reads variant vectors at the tag level to determine element boundaries but preserves all bytes verbatim. This enables bit-identical round-tripping without implementing every extra variant type (there are 20+ defined in the C++ code). ### Varint Encoding The wire format uses 7-bit variable-length integers identical to protobuf varints. Each byte carries 7 data bits in the low bits with the MSB set to 1 if more bytes follow. A uint64 requires at most 10 bytes. The implementation provides sentinel errors (`ErrVarintOverflow`, `ErrVarintEmpty`) for malformed input. ### Hardfork System (Reverse-Scan VersionAtHeight) `VersionAtHeight()` iterates all hardforks and returns the highest version whose activation height has been passed. A fork with `Height=0` is active from genesis. A fork with `Height=N` is active at heights strictly greater than N. This scan approach (rather than a sorted binary search) is deliberate: the fork list is small (7 entries) and correctness is trivially verifiable. The same list drives both `VersionAtHeight()` and `IsHardForkActive()`. ### LWMA Difficulty Adjustment The difficulty algorithm uses the LWMA (Linear Weighted Moving Average) approach: ``` nextDiff = difficultyDelta * targetInterval / timeSpan ``` The window examines up to 735 blocks (720 window + 15 lag). When fewer blocks are available (early chain), the algorithm uses whatever data exists. Division by zero is prevented by clamping the time span to a minimum of 1 second. `StarterDifficulty` (value 1) is returned when insufficient data is available. ### crypto/ -- CGo Bridge to libcryptonote Bridges Go to the upstream CryptoNote C++ crypto library via CGo. The C++ code is vendored in `crypto/upstream/` (37 files from Zano commit `fa1608cf`) and built as a static library (`libcryptonote.a`) via CMake. **Build flow:** `CMakeLists.txt` → `cmake --build` → `libcryptonote.a` → CGo links. **C API contract:** `bridge.h` defines the stable C boundary. Go code calls ONLY these functions -- no C++ types cross the boundary. All parameters are raw `uint8_t*` pointers with explicit sizes. This is the same CGo pattern used by `core/go-ai` for the MLX backend. **Compat layer:** `crypto/compat/` provides minimal stubs replacing epee/Boost dependencies (logging macros, warnings pragmas, zero-init, profile tools). The upstream files are unmodified copies; all adaptation lives in the compat headers. **Provenance:** `crypto/PROVENANCE.md` maps each vendored file to its upstream origin path and modification status, with an update workflow for tracking Zano upstream changes. **Exposed operations:** | Category | Functions | |----------|-----------| | Hashing | `FastHash` (Keccak-256) | | Key ops | `GenerateKeys`, `SecretToPublic`, `CheckKey` | | Key derivation | `GenerateKeyDerivation`, `DerivePublicKey`, `DeriveSecretKey` | | Key images | `GenerateKeyImage`, `ValidateKeyImage` | | Standard sigs | `GenerateSignature`, `CheckSignature` | | Ring sigs (NLSAG) | `GenerateRingSignature`, `CheckRingSignature` | | CLSAG (HF4+) | `GenerateCLSAGGG`, `VerifyCLSAGGG`, `VerifyCLSAGGGX`, `VerifyCLSAGGGXXG` | | Point helpers | `PointMul8`, `PointDiv8` (cofactor 1/8 premultiplication) | | Proof verification | `VerifyBPPE`, `VerifyBGE`, `VerifyZarcanum` (stubs -- Phase 4) | **Ring buffer convention:** Ring entries are flat byte arrays. CLSAG ring entries pack 32-byte public keys per dimension (GG=64B, GGX=96B, GGXXG=128B per entry). Signatures are serialised as flat buffers with documented layouts in `bridge.h`. **1/8 premultiplication:** On-chain commitments are stored premultiplied by the cofactor inverse (1/8). The `PointMul8`/`PointDiv8` helpers convert between representations. CLSAG generate takes full points; CLSAG verify takes premultiplied values. ### ADR-001: Go Shell + C++ Crypto Library This package follows ADR-001. All protocol logic, data types, serialisation, and configuration live in pure Go. The mathematically complex cryptographic primitives (ring signatures, bulletproofs, Zarcanum proofs) are delegated to the vendored C++ library in `crypto/` via CGo. This boundary keeps the pure Go code testable without a C toolchain while preserving access to battle-tested cryptographic implementations.