docs: Phase 6 wallet core documentation and integration test

Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
Claude 2026-02-20 23:32:07 +00:00
parent 11b50d0491
commit 459ff80f1f
No known key found for this signature in database
GPG key ID: AF404715446AEB41
3 changed files with 176 additions and 5 deletions

View file

@ -18,8 +18,9 @@ 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 (10 endpoints)
rpc/ Daemon JSON-RPC 2.0 client (12 endpoints)
chain/ Chain storage, indexing, and sync client (go-store backed)
wallet/ Wallet core: key management, scanning, signing, TX construction
```
### config/
@ -139,6 +140,10 @@ rather than `MAP_JON_RPC`.
- `transactions.go` -- `GetTxDetails`, `GetTransactions` (legacy).
- `mining.go` -- `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
@ -175,6 +180,54 @@ to the C++ daemon's core containers.
- Build-tagged integration test (`//go:build integration`) syncing first 10
blocks from C++ testnet daemon on `localhost:46941`.
### 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.
---
## Key Types

View file

@ -374,11 +374,70 @@ groups mapping to the C++ daemon's core containers.
**Dependencies added:** `forge.lthn.ai/core/go-store` (local replace).
## Phase 6 -- Wallet Core (Planned)
## Phase 6 -- Wallet Core
Implement `wallet/` with key management, output scanning, transaction
construction, and balance calculation. Deterministic key derivation from seed
phrases. Support for all address types.
Commit range: `5b677d1`..`11b50d0`
Added `wallet/` package implementing full send+receive wallet functionality
with interface-driven design for v1/v2+ extensibility.
### Files added
| File | Purpose |
|------|---------|
| `wallet/wordlist.go` | 1626-word Electrum mnemonic dictionary |
| `wallet/mnemonic.go` | 25-word seed phrase encode/decode with CRC32 checksum |
| `wallet/mnemonic_test.go` | Mnemonic tests (9 tests) |
| `wallet/extra.go` | TX extra parser for tags 22/14/11 |
| `wallet/extra_test.go` | Extra parsing tests (10 tests) |
| `wallet/account.go` | Account key management with Argon2id+AES-256-GCM encryption |
| `wallet/account_test.go` | Account tests (6 tests) |
| `wallet/transfer.go` | Transfer type and go-store persistence |
| `wallet/transfer_test.go` | Transfer tests (15 tests) |
| `wallet/scanner.go` | Scanner interface + V1Scanner (ECDH output detection) |
| `wallet/scanner_test.go` | Scanner tests (7 tests) |
| `wallet/signer.go` | Signer interface + NLSAGSigner (CGo ring signatures) |
| `wallet/signer_test.go` | Signer tests (4 tests) |
| `wallet/ring.go` | RingSelector interface + RPCRingSelector |
| `wallet/ring_test.go` | Ring selection tests (3 tests) |
| `wallet/builder.go` | Builder interface + V1Builder (TX construction) |
| `wallet/builder_test.go` | Builder tests (3 tests) |
| `wallet/wallet.go` | Wallet orchestrator (sync, balance, send) |
| `wallet/wallet_test.go` | Wallet orchestrator tests (2 tests) |
| `wallet/integration_test.go` | C++ testnet integration test |
| `rpc/wallet.go` | GetRandomOutputs + SendRawTransaction RPC endpoints |
| `rpc/wallet_test.go` | RPC wallet endpoint tests (4 tests) |
### Files modified
| File | Change |
|------|--------|
| `types/types.go` | Added `PublicKey.IsZero()` method |
| `crypto/bridge.h` | Added `cn_sc_reduce32()` declaration |
| `crypto/bridge.cpp` | Added `cn_sc_reduce32()` implementation |
| `crypto/crypto.go` | Added `ScReduce32()` Go wrapper |
### Key findings
- **View key derivation requires sc_reduce32.** The raw Keccak-256 of the
spend secret key must be reduced modulo the Ed25519 group order before it is
a valid scalar. Added `cn_sc_reduce32` to the CGo bridge.
- **Interface-driven design.** Four core interfaces (Scanner, Signer, Builder,
RingSelector) decouple v1 implementations from the orchestrator. Future v2+
(Zarcanum/CLSAG) implementations slot in by implementing the same interfaces.
- **go-store GetAll.** Transfer listing uses `store.GetAll(group)` which returns
all key-value pairs in a group, rather than iterating with individual Gets.
- **CryptoNote mnemonic encoding.** The 1626-word Electrum dictionary encodes
4 bytes into 3 words using modular arithmetic: `val = w1 + n*((n-w1+w2)%n) +
n*n*((n-w2+w3)%n)` where n=1626. The 25th word is a CRC32 checksum.
### Tests added
63 unit tests + 1 integration test = 64 total across wallet/ and rpc/wallet.
All passing with `-race` and `go vet`.
## Phase 7 -- Consensus Rules (Planned)

View file

@ -0,0 +1,59 @@
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// SPDX-License-Identifier: EUPL-1.2
//go:build integration
package wallet
import (
"testing"
store "forge.lthn.ai/core/go-store"
"forge.lthn.ai/core/go-blockchain/chain"
"forge.lthn.ai/core/go-blockchain/rpc"
)
func TestWalletIntegration(t *testing.T) {
client := rpc.NewClient("http://localhost:46941")
s, err := store.New(":memory:")
if err != nil {
t.Fatal(err)
}
defer s.Close()
c := chain.New(s)
// Sync chain first.
if err := c.Sync(client); err != nil {
t.Fatalf("chain sync: %v", err)
}
acc, err := GenerateAccount()
if err != nil {
t.Fatal(err)
}
w := NewWallet(acc, s, c, client)
if err := w.Sync(); err != nil {
t.Fatal(err)
}
confirmed, locked, err := w.Balance()
if err != nil {
t.Fatal(err)
}
t.Logf("Balance: confirmed=%d, locked=%d", confirmed, locked)
transfers, err := w.Transfers()
if err != nil {
t.Fatal(err)
}
t.Logf("Transfers: %d", len(transfers))
}