Document NLSAG verification, P2P sync protocol, and shared block processing. Co-Authored-By: Charon <charon@lethean.io>
578 lines
25 KiB
Markdown
578 lines
25 KiB
Markdown
# 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)
|
|
rpc/ Daemon JSON-RPC 2.0 client (12 endpoints)
|
|
chain/ Chain storage, indexing, and sync client (go-store backed)
|
|
consensus/ Three-layer block/transaction validation (structural, economic, crypto)
|
|
wallet/ Wallet core: key management, scanning, signing, TX construction
|
|
mining/ Solo PoW miner (daemon RPC, RandomX nonce grinding)
|
|
```
|
|
|
|
### 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). `BlockCompleteEntry` for block + transaction
|
|
blob pairs. `BlockContextInfo` for chain entry response objects.
|
|
- **getobjects.go** -- `RequestGetObjects` (2003) and `ResponseGetObjects`
|
|
(2004) types for fetching blocks by hash during P2P sync.
|
|
- **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.
|
|
|
|
### rpc/
|
|
|
|
Typed JSON-RPC 2.0 client for querying the Lethean daemon. The `Client` struct
|
|
wraps `net/http` and provides Go methods for 11 core daemon endpoints.
|
|
|
|
Eight endpoints use JSON-RPC 2.0 via `/json_rpc`. Two endpoints (`GetHeight`,
|
|
`GetTransactions`) use legacy JSON POST to dedicated URI paths (`/getheight`,
|
|
`/gettransactions`), as the C++ daemon registers these with `MAP_URI_AUTO_JON2`
|
|
rather than `MAP_JON_RPC`.
|
|
|
|
**Client transport:**
|
|
- `client.go` -- `Client` struct with `call()` (JSON-RPC 2.0) and `legacyCall()`
|
|
(plain JSON POST). `RPCError` type for daemon error codes.
|
|
- `types.go` -- `BlockHeader`, `DaemonInfo`, `BlockDetails`, `TxInfo` shared types.
|
|
|
|
**Endpoints:**
|
|
- `info.go` -- `GetInfo`, `GetHeight` (legacy), `GetBlockCount`.
|
|
- `blocks.go` -- `GetLastBlockHeader`, `GetBlockHeaderByHeight`,
|
|
`GetBlockHeaderByHash`, `GetBlocksDetails`.
|
|
- `transactions.go` -- `GetTxDetails`, `GetTransactions` (legacy).
|
|
- `mining.go` -- `GetBlockTemplate`, `SubmitBlock`.
|
|
|
|
**Wallet endpoints:**
|
|
- `wallet.go` -- `GetRandomOutputs` (for ring decoy selection via `/getrandom_outs1`)
|
|
and `SendRawTransaction` (for transaction submission via `/sendrawtransaction`).
|
|
|
|
**Testing:**
|
|
- Mock HTTP server tests for all endpoints and error paths.
|
|
- Build-tagged integration test (`//go:build integration`) against C++ testnet
|
|
daemon on `localhost:46941`. Verifies genesis block hash matches Phase 1
|
|
result (`cb9d5455...`).
|
|
|
|
### chain/
|
|
|
|
Stores and indexes the Lethean blockchain by syncing from a C++ daemon via RPC.
|
|
Uses go-store (pure-Go SQLite) for persistence with five storage groups mapping
|
|
to the C++ daemon's core containers.
|
|
|
|
**Storage schema:**
|
|
- `blocks` -- blocks by height (zero-padded key), JSON metadata + hex blob.
|
|
- `block_index` -- hash-to-height reverse index.
|
|
- `transactions` -- tx hash to JSON metadata + hex blob.
|
|
- `spent_keys` -- key image to block height (double-spend index).
|
|
- `outputs:{amount}` -- global output index per amount.
|
|
|
|
**Core operations:**
|
|
- `chain.go` -- `Chain` struct with `New()`, `Height()`, `TopBlock()`.
|
|
- `store.go` -- `PutBlock`, `GetBlockByHeight/Hash`, `PutTransaction`,
|
|
`GetTransaction`, `HasTransaction`.
|
|
- `index.go` -- `MarkSpent`, `IsSpent`, `PutOutput`, `GetOutput`, `OutputCount`.
|
|
- `validate.go` -- Header validation (previous block linkage, height sequence,
|
|
block size).
|
|
- `sync.go` -- `Sync(client)` blocking RPC poll loop. Fetches blocks in batches
|
|
of 10, decodes wire blobs, validates headers, indexes transactions/outputs/
|
|
key images, verifies block hashes. `processBlockBlobs()` shared validation
|
|
path for both RPC and P2P sync. `resolveBlockBlobs()` reconstructs block data
|
|
from daemon JSON responses. Context cancellation and progress logging for
|
|
long syncs.
|
|
|
|
**P2P sync:**
|
|
- `P2PConnection` interface abstracting peer communication (request/response
|
|
over any transport).
|
|
- `LevinP2PConn` adapter wrapping a Levin TCP connection to satisfy
|
|
`P2PConnection`.
|
|
- `P2PSync()` state machine running the REQUEST_CHAIN / REQUEST_GET_OBJECTS
|
|
loop until the peer has no more blocks.
|
|
- `SparseChainHistory()` builds exponentially-spaced block hash lists matching
|
|
the C++ `get_short_chain_history()` algorithm.
|
|
- `GetRingOutputs()` callback for ring signature verification during sync.
|
|
|
|
**Testing:**
|
|
- Unit tests with go-store `:memory:` for all CRUD operations and validation.
|
|
- Mock RPC server sync tests.
|
|
- Build-tagged integration test (`//go:build integration`) syncing first 10
|
|
blocks from C++ testnet daemon on `localhost:46941`.
|
|
- P2P sync integration test against C++ testnet daemon on `localhost:46942`.
|
|
|
|
### consensus/
|
|
|
|
Standalone consensus validation package implementing three-layer block and
|
|
transaction verification. All functions are pure — they take types, config,
|
|
and height, returning errors. No dependency on `chain/` or any storage layer.
|
|
|
|
**Layer 1 — Structural (no crypto):**
|
|
- `ValidateTransaction()` — 8 ordered checks matching C++ `validate_tx_semantic()`:
|
|
blob size, input count, input types, output validation, money overflow,
|
|
key image uniqueness, extra parsing, balance check.
|
|
|
|
**Layer 2 — Economic:**
|
|
- `BaseReward()` / `BlockReward()` / `MinerReward()` — fixed 1 LTHN/block with
|
|
size penalty using 128-bit arithmetic (`math/bits.Mul64`). Pre-HF4 fees go to
|
|
miner; post-HF4 fees are burned.
|
|
- `TxFee()` — fee extraction with overflow detection.
|
|
- `ValidateBlockReward()` — miner output sum vs expected reward.
|
|
|
|
**Layer 3 — Cryptographic (CGo):**
|
|
- `CheckDifficulty()` / `CheckPoWHash()` — 256-bit PoW hash comparison via
|
|
RandomX (vendored in `crypto/randomx/`, key `"LetheanRandomXv1"`).
|
|
- `VerifyTransactionSignatures()` — NLSAG (pre-HF4) and CLSAG (post-HF4)
|
|
signature verification. `verifyV1Signatures()` performs real NLSAG ring
|
|
signature verification via CGo, using a `RingOutputsFn` callback to fetch
|
|
ring member public keys from chain storage.
|
|
|
|
**Block validation:**
|
|
- `CheckTimestamp()` — future time limit (7200s PoW, 1200s PoS) + median-of-60.
|
|
- `ValidateMinerTx()` — coinbase structure (1 input = PoW, 2 inputs = PoS).
|
|
- `ValidateBlock()` — orchestrator combining timestamp, miner tx, and reward.
|
|
- `IsPoS()` — flags bit 0 check.
|
|
|
|
All validation is hardfork-aware via `config.IsHardForkActive()` with the fork
|
|
schedule passed as a parameter. The `chain/sync.go` integration calls
|
|
`ValidateMinerTx()` and `ValidateTransaction()` during sync, with optional
|
|
signature verification via `SyncOptions.VerifySignatures`.
|
|
|
|
### wallet/
|
|
|
|
Full send+receive wallet for the Lethean blockchain. Uses interface-driven design
|
|
with four core abstractions so v1 (NLSAG) implementations ship now and v2+
|
|
(Zarcanum/CLSAG) slot in later without changing callers.
|
|
|
|
**Core interfaces:**
|
|
- `Scanner` -- detects outputs belonging to a wallet using ECDH derivation.
|
|
`V1Scanner` implements v0/v1 output detection.
|
|
- `Signer` -- produces ring signatures for transaction inputs. `NLSAGSigner`
|
|
wraps the CGo NLSAG ring signature primitives.
|
|
- `Builder` -- constructs signed transactions. `V1Builder` handles v1
|
|
transactions with decoy ring selection and ECDH output derivation.
|
|
- `RingSelector` -- picks decoy outputs for ring signatures.
|
|
`RPCRingSelector` fetches random outputs from the daemon via RPC.
|
|
|
|
**Account management:**
|
|
- `account.go` -- `Account` struct with spend and view key pairs. Key
|
|
derivation: `viewSecret = sc_reduce32(Keccak256(spendSecret))`, matching
|
|
the C++ `account_base::generate()` pattern. Three creation methods:
|
|
`GenerateAccount()`, `RestoreFromSeed()`, `RestoreViewOnly()`.
|
|
- `mnemonic.go` -- 25-word CryptoNote mnemonic encoding/decoding using the
|
|
Electrum 1626-word dictionary. CRC32 checksum word.
|
|
- Persistence with Argon2id (time=3, mem=64MB, threads=4) + AES-256-GCM
|
|
encryption via go-store.
|
|
|
|
**Transaction extra:**
|
|
- `extra.go` -- minimal parser for three wallet-critical variant tags: tx
|
|
public key (tag 22), unlock time (tag 14), derivation hint (tag 11).
|
|
Unknown tags skipped. Raw bytes preserved for round-tripping.
|
|
|
|
**Wallet orchestrator:**
|
|
- `wallet.go` -- `Wallet` struct tying scanning, building, and sending.
|
|
`Sync()` scans from last checkpoint to chain tip, detecting owned outputs
|
|
and spent key images. `Balance()` returns confirmed vs locked amounts.
|
|
`Send()` performs largest-first coin selection, builds, signs, and submits
|
|
transactions via RPC. `Transfers()` lists all tracked outputs.
|
|
|
|
**Transfer storage:**
|
|
- `transfer.go` -- `Transfer` struct persisted as JSON in go-store group
|
|
`transfers`, keyed by key image hex. `IsSpendable()` checks spend status,
|
|
coinbase maturity (MinedMoneyUnlockWindow=10), and unlock time.
|
|
|
|
**Testing:**
|
|
- Unit tests with go-store `:memory:` for all components.
|
|
- Build-tagged integration test (`//go:build integration`) syncing from
|
|
C++ testnet daemon and verifying balance/transfers.
|
|
|
|
### mining/
|
|
|
|
Solo PoW miner that talks to a C++ daemon via JSON-RPC. Single-threaded
|
|
mining loop: fetches block templates, computes a header mining hash once
|
|
per template, then grinds nonces with RandomX until a solution is found
|
|
or the chain advances.
|
|
|
|
**Core types:**
|
|
- `Config` -- daemon URL, wallet address, poll interval, callbacks.
|
|
- `Miner` -- mining loop with `Start(ctx)` (blocking) and `Stats()` (lock-free).
|
|
- `TemplateProvider` -- interface satisfied by `rpc.Client` for testability.
|
|
|
|
**Mining flow:**
|
|
1. `GetBlockTemplate(walletAddr)` -- fetches template from daemon.
|
|
2. `HeaderMiningHash(block)` -- Keccak-256 of `BlockHashingBlob` with nonce=0.
|
|
3. Nonce loop: `RandomXHash("LetheanRandomXv1", headerHash || nonce_LE)`.
|
|
4. `CheckDifficulty(powHash, difficulty)` -- solution found?
|
|
5. `SubmitBlock(hexBlob)` -- submits the solved block.
|
|
|
|
**Template refresh:** polls `GetInfo()` every `PollInterval` (default 3s) to
|
|
detect new blocks. Re-fetches the template when the chain height advances.
|
|
|
|
**Testing:** Mock `TemplateProvider` for unit tests. Build-tagged integration
|
|
test against C++ testnet daemon on `localhost:46941`.
|
|
|
|
---
|
|
|
|
## 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.
|