2026-03-11 13:02:40 +00:00
|
|
|
---
|
|
|
|
|
title: Development Guide
|
|
|
|
|
description: How to build, test, and contribute to go-crypt.
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# Development Guide
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
## Prerequisites
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
- **Go 1.26** or later (the module declares `go 1.26.0`).
|
|
|
|
|
- A Go workspace (`go.work`) that resolves the local dependencies:
|
|
|
|
|
`forge.lthn.ai/core/go`, `forge.lthn.ai/core/go-store`,
|
|
|
|
|
`forge.lthn.ai/core/go-io`, `forge.lthn.ai/core/go-log`, and
|
|
|
|
|
`forge.lthn.ai/core/cli`. If you are working outside the full monorepo,
|
|
|
|
|
create a `go.work` at the parent directory pointing to your local
|
|
|
|
|
checkouts.
|
|
|
|
|
- No C toolchain, CGo, or system libraries are required. All cryptographic
|
|
|
|
|
operations use pure Go implementations.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
## Build and Test
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Run all tests
|
|
|
|
|
go test ./...
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
# Run with race detector (required before committing)
|
2026-02-20 15:01:55 +00:00
|
|
|
go test -race ./...
|
|
|
|
|
|
|
|
|
|
# Run a single test by name
|
|
|
|
|
go test -v -run TestName ./...
|
|
|
|
|
|
|
|
|
|
# Run tests in a specific package
|
|
|
|
|
go test ./auth/...
|
|
|
|
|
go test ./crypt/...
|
|
|
|
|
go test ./trust/...
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
# Static analysis (must be clean before committing)
|
2026-02-20 15:01:55 +00:00
|
|
|
go vet ./...
|
|
|
|
|
|
|
|
|
|
# Run benchmarks
|
|
|
|
|
go test -bench=. -benchmem ./crypt/...
|
|
|
|
|
go test -bench=. -benchmem ./trust/...
|
2026-03-11 13:02:40 +00:00
|
|
|
|
|
|
|
|
# Extended benchmark run
|
|
|
|
|
go test -bench=. -benchmem -benchtime=3s ./crypt/...
|
2026-02-20 15:01:55 +00:00
|
|
|
```
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
If using the `core` CLI:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
core go test
|
|
|
|
|
core go test --run TestName
|
|
|
|
|
core go qa # fmt + vet + lint + test
|
|
|
|
|
core go qa full # + race, vuln, security
|
|
|
|
|
```
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
## Repository Layout
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
go-crypt/
|
2026-03-11 13:02:40 +00:00
|
|
|
├── auth/ Authentication: Authenticator, sessions, key management
|
|
|
|
|
├── cmd/
|
|
|
|
|
│ ├── crypt/ CLI commands: encrypt, decrypt, hash, keygen, checksum
|
|
|
|
|
│ └── testcmd/ Test runner commands
|
|
|
|
|
├── crypt/ Symmetric encryption, hashing, key derivation
|
|
|
|
|
│ ├── chachapoly/ Standalone ChaCha20-Poly1305 AEAD
|
|
|
|
|
│ ├── lthn/ RFC-0004 quasi-salted deterministic hash
|
|
|
|
|
│ ├── openpgp/ core.Crypt service wrapper
|
2026-02-20 15:01:55 +00:00
|
|
|
│ ├── pgp/ OpenPGP primitives
|
2026-03-11 13:02:40 +00:00
|
|
|
│ └── rsa/ RSA-OAEP-SHA256
|
|
|
|
|
├── docs/ Documentation
|
|
|
|
|
├── trust/ Agent trust model, policy engine, audit log
|
2026-02-20 15:01:55 +00:00
|
|
|
├── go.mod
|
|
|
|
|
└── go.sum
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Test Patterns
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
Tests use `github.com/stretchr/testify` (`assert` and `require`). The
|
|
|
|
|
naming convention uses three suffixes to categorise test intent:
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
| Suffix | Purpose |
|
|
|
|
|
|--------|---------|
|
2026-03-11 13:02:40 +00:00
|
|
|
| `_Good` | Happy path -- expected success |
|
|
|
|
|
| `_Bad` | Expected failure -- invalid input, wrong credentials, not-found errors |
|
|
|
|
|
| `_Ugly` | Edge cases -- panics, zero values, empty inputs, extreme lengths |
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
```go
|
2026-03-11 13:02:40 +00:00
|
|
|
func TestLogin_Good(t *testing.T) {
|
|
|
|
|
// Register a user, log in with correct password, verify session
|
|
|
|
|
}
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
func TestLogin_Bad(t *testing.T) {
|
|
|
|
|
// Attempt login with wrong password, verify rejection
|
|
|
|
|
}
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
func TestLogin_Ugly(t *testing.T) {
|
|
|
|
|
// Empty password, very long input, Unicode edge cases
|
|
|
|
|
}
|
|
|
|
|
```
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
### Concurrency Tests
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
Concurrent tests spawn 10 goroutines via a `sync.WaitGroup` and use
|
|
|
|
|
`t.Parallel()`. The race detector (`go test -race`) must pass for all
|
|
|
|
|
concurrent tests. Examples include concurrent session creation, concurrent
|
|
|
|
|
registry access, and concurrent policy evaluation.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
### Benchmarks
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
Benchmarks live in `bench_test.go` files alongside the packages they cover:
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
- `crypt/bench_test.go`: Argon2id derivation, ChaCha20 and AES-GCM at
|
|
|
|
|
1KB and 1MB payloads, HMAC-SHA256, HMAC verification.
|
|
|
|
|
- `trust/bench_test.go`: policy evaluation with 100 agents, registry
|
|
|
|
|
get, registry register.
|
|
|
|
|
|
|
|
|
|
**Note**: The Argon2id KDF is intentionally slow (~200ms on typical
|
|
|
|
|
hardware). This is a security property, not a performance defect. Do not
|
|
|
|
|
optimise KDF parameters without understanding the security implications.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
## Adding a New Cryptographic Primitive
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
1. Add the implementation in the appropriate sub-package under `crypt/`.
|
2026-02-20 15:01:55 +00:00
|
|
|
2. Write tests covering `_Good`, `_Bad`, and `_Ugly` cases.
|
|
|
|
|
3. Add a benchmark if the function is called on hot paths.
|
|
|
|
|
4. Update `docs/architecture.md` with the algorithm reference entry.
|
|
|
|
|
5. Run `go vet ./...` and `go test -race ./...` before committing.
|
|
|
|
|
|
|
|
|
|
## Adding a New Trust Capability
|
|
|
|
|
|
|
|
|
|
1. Add the `Capability` constant in `trust/trust.go`.
|
2026-03-11 13:02:40 +00:00
|
|
|
2. If the capability is repository-scoped, update `isRepoScoped()` in
|
|
|
|
|
`trust/policy.go`.
|
2026-02-20 15:01:55 +00:00
|
|
|
3. Update the default policies in `loadDefaults()` in `trust/policy.go`.
|
|
|
|
|
4. Add tests covering all three tiers.
|
|
|
|
|
5. Update the capability table in `docs/architecture.md`.
|
|
|
|
|
|
|
|
|
|
## Coding Standards
|
|
|
|
|
|
|
|
|
|
### Language
|
|
|
|
|
|
|
|
|
|
UK English throughout: _colour_, _organisation_, _centre_, _artefact_,
|
|
|
|
|
_licence_ (noun), _license_ (verb), _behaviour_, _initialise_, _serialise_.
|
|
|
|
|
|
|
|
|
|
### Go Style
|
|
|
|
|
|
|
|
|
|
- Every exported function and type must have a doc comment.
|
2026-03-11 13:02:40 +00:00
|
|
|
- Error strings are lowercase and do not end with a full stop, per Go
|
|
|
|
|
convention.
|
|
|
|
|
- Use the `core.E(op, msg, err)` helper for contextual error wrapping:
|
|
|
|
|
`op` is `"package.Function"`, `msg` is a brief lowercase description.
|
|
|
|
|
- Import groups, separated by blank lines: stdlib, then `forge.lthn.ai/core`,
|
|
|
|
|
then third-party.
|
|
|
|
|
- Avoid `any` except at interface boundaries. Prefer explicit type
|
|
|
|
|
assertions.
|
|
|
|
|
|
|
|
|
|
### Cryptographic Safety
|
|
|
|
|
|
|
|
|
|
- All randomness from `crypto/rand`. Never use `math/rand` for
|
|
|
|
|
cryptographic purposes.
|
|
|
|
|
- Use `crypto/subtle.ConstantTimeCompare` for any comparison of secret
|
|
|
|
|
material (MACs, password hashes, session tokens).
|
|
|
|
|
- Never log or return secrets in error messages. Keep error strings
|
|
|
|
|
generic: `"invalid password"`, `"session not found"`, `"failed to
|
|
|
|
|
decrypt"`.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
### Licence
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
All files are licenced under EUPL-1.2. Do not add files under a different
|
|
|
|
|
licence.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
## Commit Convention
|
|
|
|
|
|
|
|
|
|
Commits follow the Conventional Commits specification:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
type(scope): short imperative description
|
|
|
|
|
|
|
|
|
|
Optional body explaining motivation and context.
|
|
|
|
|
|
|
|
|
|
Co-Authored-By: Virgil <virgil@lethean.io>
|
|
|
|
|
```
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
**Types**: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
**Scopes** match package names: `auth`, `crypt`, `trust`, `pgp`, `lthn`,
|
|
|
|
|
`rsa`, `openpgp`, `chachapoly`.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
feat(auth): add SQLite session store for crash recovery
|
|
|
|
|
fix(trust): reject empty ScopedRepos as no-access for Tier 2
|
|
|
|
|
test(crypt): add benchmark suite for Argon2 and ChaCha20
|
2026-03-11 13:02:40 +00:00
|
|
|
docs(trust): document approval queue workflow
|
2026-02-20 15:01:55 +00:00
|
|
|
```
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
## Pushing to Forge
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
The canonical remote is `forge.lthn.ai`. Push via SSH only:
|
2026-02-20 15:01:55 +00:00
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git push forge main
|
|
|
|
|
# remote: ssh://git@forge.lthn.ai:2223/core/go-crypt.git
|
|
|
|
|
```
|
|
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
HTTPS authentication is not configured for this repository.
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
## Local Dependencies
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
The `go.mod` depends on several `forge.lthn.ai/core/*` modules. These are
|
|
|
|
|
resolved through the Go workspace (`~/Code/go.work`). Do not modify the
|
|
|
|
|
replace directives in `go.mod` directly -- use the workspace file instead.
|
|
|
|
|
|
|
|
|
|
| Module | Local Path | Purpose |
|
|
|
|
|
|--------|-----------|---------|
|
|
|
|
|
| `forge.lthn.ai/core/go` | `../go` | Framework: `core.Crypt` interface, `io.Medium` |
|
|
|
|
|
| `forge.lthn.ai/core/go-store` | `../go-store` | SQLite KV store for session persistence |
|
|
|
|
|
| `forge.lthn.ai/core/go-io` | `../go-io` | `io.Medium` storage abstraction |
|
|
|
|
|
| `forge.lthn.ai/core/go-log` | `../go-log` | `core.E()` contextual error wrapping |
|
|
|
|
|
| `forge.lthn.ai/core/cli` | `../cli` | CLI framework for `cmd/crypt` commands |
|
|
|
|
|
|
|
|
|
|
## Known Limitations
|
|
|
|
|
|
|
|
|
|
For a full list of known limitations and open security findings, see
|
|
|
|
|
[history.md](history.md).
|
|
|
|
|
|
|
|
|
|
Key items:
|
2026-02-20 15:01:55 +00:00
|
|
|
|
2026-03-11 13:02:40 +00:00
|
|
|
- **Dual ChaCha20 implementations**: `crypt/symmetric.go` and
|
|
|
|
|
`crypt/chachapoly/` are nearly identical. Consolidation would reduce
|
|
|
|
|
duplication but requires updating all importers.
|
|
|
|
|
- **Hardware key interface**: contract-only, no concrete implementations.
|
|
|
|
|
- **Session cleanup logging**: uses `fmt.Printf` rather than a structured
|
|
|
|
|
logger. Callers needing structured logs should wrap the cleanup goroutine.
|
|
|
|
|
- **Rate limiting**: the `Agent.RateLimit` field is stored but never
|
|
|
|
|
enforced. Enforcement belongs in a higher-level middleware layer.
|