8.9 KiB
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)
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 valuesPublicKey(32 bytes) -- Ed25519 public keysSecretKey(32 bytes) -- Ed25519 secret keysKeyImage(32 bytes) -- double-spend detection imagesSignature(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 primitives. Currently implements CryptoNote varint encoding (7-bit LEB128 with MSB continuation). All encoding must be bit-identical to the C++ reference implementation.
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.
Key Types
ChainConfig
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
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
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
type BlockHeader struct {
MajorVersion uint8
MinorVersion uint8
Timestamp uint64
PrevID Hash
Nonce uint64
}
type Block struct {
BlockHeader
MinerTx Transaction
TxHashes []Hash
}
type Transaction struct {
Version uint8
UnlockTime uint64
Vin []TxInput
Vout []TxOutput
Extra []byte
}
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 0xFF) and TxInputToKey (standard
spend with ring signature, tag 0x02).
Output types: TxOutputBare (transparent, tag 0x02) and TxOutputZarcanum
(confidential with Pedersen commitments, tag 0x03).
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
- Encode the address prefix as a CryptoNote varint
- Append the 32-byte spend public key
- Append the 32-byte view public key
- Append the 1-byte flags field
- Compute Keccak-256 over bytes 1-4, take the first 4 bytes as checksum
- Append the 4-byte checksum
- 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.
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.
ADR-001: Go Shell + C++ Crypto Library
This package follows ADR-001. All protocol logic, data types, serialisation, and configuration live in pure Go. Only the mathematically complex cryptographic primitives (ring signatures, bulletproofs, Zarcanum proofs) will be delegated to a cleaned C++ library via CGo in later phases. This boundary keeps the Go code testable without a C toolchain while preserving access to battle-tested cryptographic implementations.