Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Fabric Agents
See also [DEVELOPERS.md](DEVELOPERS.md) (repo layout, tests) and [docs/PRODUCTION.md](docs/PRODUCTION.md) (release gate).
See also [DEVELOPERS.md](DEVELOPERS.md) (repo layout, tests), [docs/PRODUCTION.md](docs/PRODUCTION.md) (release gate), and [docs/TYPES_AND_SERVICES.md](docs/TYPES_AND_SERVICES.md) (suite `types/` + `services/` layering vs http / Hub / Passport / GoonCitizen).

## Release posture
- **Target:** `0.1.0-RC1` reference client — not a production-hardened VM claim
Expand Down
2 changes: 1 addition & 1 deletion AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ audit report.
| Peer misbehavior / scoring | Implemented (integrity, pin, session, contract ops, logical-register, nest cap, temp ban) — see [SECURITY.md](SECURITY.md) |
| P2P_RELAY amplification | Mitigated: bit-identical outer forward + nest depth cap + relay-as-is pin; gossip hop remains advisory |
| Chat mesh amplify | Mitigated: per-origin relay budget (`CHAT_MAX_RELAYS_*`) |
| Oversize wire frames | Mitigated: drop before parse/crypto (`HEADER_SIZE + MAX_MESSAGE_SIZE`) |
| Oversize / undersize wire frames | Mitigated: drop before parse/crypto (`< HEADER_SIZE` or `> HEADER_SIZE + MAX_MESSAGE_SIZE`); unparseable buffers drop without score/ban |
| Inventory HTLC address spoof | Mitigated: rebuild+match (`validateInventoryHtlcOffer`); AMP signer binding when present |
| Paid `/confirm` without L1 proof | Mitigated: fail-closed local verify or Hub `ConfirmInventoryHtlcPayment` (`cliDocumentExchange`) |
| Key reveal from hash echo | Mitigated: `authorizeDocumentKeyReveal` requires `settlementId`/`txid`; `forceReveal` opt-in only; inbound reveal requires key preimage + claim-after-open |
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
Recent changes to Fabric Core.

## 2026-08-14
- **Peer IPv6 dial:** `_connect('[::1]:port')` strips the extra URL brackets Node leaves on `url.hostname` so `net.createConnection` gets `::1` and the dial key stays `[::1]:port`.
- **Chat shoutbox:** Fabric TUI sends/receives first-class UTF-8 `P2P_CHAT_MESSAGE` (Peer `{ text }` + AMP signer, `P2P_PEER_ALIAS` nicknames). Shared helper `functions/fabricChatText`. Cap matches Peer `P2P_CHAT_MAX_CHARS` (2000).
- **Peer/Service memory:** `Service.commit()` emits a compact `{ type, clock, id, patchCount }` and caps `history` (`SERVICE_COMMIT_HISTORY_MAX` / `commitHistoryMax`). It no longer wraps full `state` in an Actor (JSON.parse of Hub collections on every write). Peer isolates `settings.state` maps (no Hub `collections` alias), does not `commit()` on `_writeFabric` / `_registerActor` / every inbound frame, and drops ephemeral `host:port` actors on `_disconnect` / `_destroyFabric`. `Peer#beat` no longer snapshots full state.
- **Operator home env:** `FABRIC_SEED` is the raw BIP32 seed **hex** (16–64 bytes); `FABRIC_MNEMONIC` is the BIP39 phrase; `FABRIC_XPRV` remains preferred. `Key` `FROM_SEED` accepts hex (legacy mnemonic in `seed` still works). `Environment` reads `FABRIC_MNEMONIC`. Helpers: `functions/fabricKeyMaterial`, `functions/fabricHomeEnv` (`~/.fabric/env` + `~/.fabric/hub-admin-token` Schnorr `OP_IDENTITY` admin Token), `functions/fabricWalletIdentity` (sealed `wallet.json` via `FABRIC_PASSWORD`). `node scripts/ensure-home-env.js` writes those files without printing secrets.
- **RC1 first-tier contract:** `tests/rc1.first-tier.contract.js` locks the four production-readiness green domains (wire integrity, gossip/peering bounds, scoring/bans, identity/wallets) at the hostile-mesh bar. Peer drops **undersize** (`< HEADER_SIZE`) and unparseable frames before parse — truncated NOISE chunks are not body-hash mismatches and do not hard-ban.
- **IdentityCrossSign lift:** `functions/identityCrossSign.js`, `fabricIdentitySchnorr.js`, `identityCrossSignVerify.js` (+ `.d.ts`) are the canonical gossip strings and BIP340 helpers. HTTP site-login / device-link stay in `@fabric/http` and re-export these leaves. `signCrossSign` binds `localPubkey` to `fabricKey.pubkey` (raw HD `Key` and Passport `{ privateKeyHex, xpub }` round-trip). Canonical strings require compressed or x-only hex pubkeys (no `:` in fields); builders keep only a 64-hex nonce; `signCrossSign` rejects unknown `kind`. `fabricIdentityIdFromPubkeyHex` requires a compressed 66-hex public key. `.d.ts` files declare real arities / `ok` unions. `createdAt` is unsigned.
- **Peer dial storms:** `_fillPeerSlots` waits `PEER_CANDIDATE_RETRY_MS` (60s) before redialing the same candidate; refused TCP no longer constructs NOISE (shared handshake EventEmitter was leaking listeners). Transient `ECONNREFUSED` is recognized from the error message when Node omits `error.code`. `_connect` skips in-flight `_outboundDialTargets` so overlapping `connectTo` / reconnect cannot open duplicate sockets before `connections[target]` is set. Dial keys are canonical `host:port` (IPv6 bracketed), so `pubkey@host:port` cannot open a second TCP/NOISE session beside `host:port`. Candidate retry timestamps are pruned on queue eviction / expiry and capped to `maxCandidates`; `candidateRetryMs` must be finite and `> 0`. `_disconnect` and inbound encrypt-end / banned-static paths tear down NOISE. Outbound `connect` setup is try/caught so a handshake throw cannot leave the dial target stuck.
- **Wallet writes:** `_writeWalletDocument` uses a pid + random tmp suffix so concurrent writes in one process cannot clobber the same `.tmp`.
- **Peer `contract:message`:** `messageHex` is a getter so hot paths that ignore the wire hex skip `toBuffer()`.
- **Tests:** coverage for `--password=VALUE` (`functions/cliPasswordArgv`), GroupChange `signers.set` vote bind, wallet atomic write / touchWallet truncate, advertised vs verified peering suppress, Beacon persist-fail retain, `Environment.stop()` key wipe, and related lock/setup fail-closed paths.
- **Codacy (PR #183):** timing-safe setup password confirm; setup TUI treats cancel as a non-string password (no `== null`); hallmark hex length uses a literal class (no `new RegExp`); tier `when` paths skip `__proto__`/`constructor`/`prototype`; BIP65 lock constants avoid 32-bit hex literals; Semgrep/Opengrep exclude path-hardened `fabricSetup` / `environment` (containment already tested).
- **Shutdown / create:** `Environment.stop()` calls `lockWallet()` so seed/xprv/plaintext keys wipe even when the idle-lock handler was never installed; `touchWallet` creates a missing file with exclusive `wx` and does not truncate on `EEXIST`.
Expand Down
2 changes: 1 addition & 1 deletion DEVELOPERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Working from a **git checkout** (not the global package) is best when you are ch
## Repository layout
| Path | Role |
|------|------|
| `types/` | ES6 **classes** — `Actor`, `Peer`, `Service`, `Store`, `Message`, etc. CommonJS (`require`) throughout. |
| `types/` | ES6 **classes** — `Actor`, `Peer`, `Service`, `Store`, `Message`, etc. CommonJS (`require`) throughout. Cross-package homes: **[docs/TYPES_AND_SERVICES.md](docs/TYPES_AND_SERVICES.md)**. |
| `services/` | Long-running **integrations** (Bitcoin RPC, Lightning stubs, ZMQ, …) built on `Service`. |
| `contracts/` | Language snippets, traces, and tooling (e.g. type dependency graph). |
| `scripts/` | CLI entrypoints, doc helpers (`list-jsdoc-type-files.js`, `remove-legacy-types.sh`). |
Expand Down
6 changes: 6 additions & 0 deletions PUBLIC_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Password-sealed JSON and in-memory lock sessions (CLI / setup wallets):
`@fabric/core/functions/sealedBlob`, `@fabric/core/functions/identityLock`
(ambient stubs `functions/sealedBlob.d.ts` / `functions/identityLock.d.ts`).

Protocol identity Schnorr + IdentityCrossSign (device-link gossip strings):
`@fabric/core/functions/fabricIdentitySchnorr`,
`@fabric/core/functions/identityCrossSign`,
`@fabric/core/functions/identityCrossSignVerify`. HTTP site-login / device-link
handlers stay in `@fabric/http` and re-export these leaves.

### Services

`@fabric/core/services/bitcoin`, `@fabric/core/services/lightning` — optional RPC
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Exact wire duplicates remain a silent drop (no score change). Wire-hash dedup re
`P2P_CHAT_MESSAGE` still emits locally, but mesh `relayFrom` is **per-origin rate-limited** (`CHAT_MAX_RELAYS_PER_ORIGIN_PER_MINUTE` / `settings.chat.maxRelaysPerOriginPerMinute`, default 30/min).

## Inbound frame size
Wire frames larger than `HEADER_SIZE + MAX_MESSAGE_SIZE` (override body via `settings.maxMessageSize`) are dropped **before** parse / body-hash / signature work.
Wire frames larger than `HEADER_SIZE + MAX_MESSAGE_SIZE` (override body via `settings.maxMessageSize`) **or shorter than `HEADER_SIZE`** are dropped **before** parse / body-hash / signature work. Unparseable buffers are dropped the same way (no score / ban). Truncated frames are not treated as body-hash mismatches — that would hard-ban a TCP peer for a partial NOISE chunk.

## Strict Protocol V1 (test contract)
Adversarial Peer coverage under the assumption that **NOISE payloads are raw, well-formed Fabric Messages** (not random bytes) lives in [`tests/protocol-v1/`](tests/protocol-v1/README.md): opcode × delivery matrix (`direct` / foreign `P2P_RELAY` / `P2P_FORWARD` peel), unknown-opcode policy (`UNKNOWN_MESSAGE` — must not alias to `P2P_BASE_MESSAGE`), multi-origin collusion budgets, and a typed live NOISE storm. Parser crash fuzz remains in `tests/fuzz/` and is **not** counted as semantic adversarial coverage.
Expand Down
15 changes: 14 additions & 1 deletion constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ const MAX_CHANNEL_VALUE = 100000000;

// Machine Constraints
const MACHINE_MAX_MEMORY = MAX_MEMORY_ALLOC * MAX_MESSAGE_SIZE;
const MAX_CHAT_MESSAGE_LENGTH = 2048;
/** Matches Peer `P2P_CHAT_MAX_CHARS` so TUI send is not dropped on the mesh. */
const MAX_CHAT_MESSAGE_LENGTH = 2000;

// Playnet
const FABRIC_PLAYNET_ADDRESS = ''; // unset until a published playnet P2TR deposit address is chosen
Expand Down Expand Up @@ -194,6 +195,11 @@ const PEERING_OFFER_MAX_RELAYS_PER_ORIGIN_PER_MINUTE = 60;
const PEERING_OFFER_MAX_PAYLOAD_CACHE = 50000;
/** Max queued connection candidates from {@link P2P_PEERING_OFFER} (FIFO eviction). */
const PEER_MAX_CANDIDATES_QUEUE = 128;
/**
* Minimum delay before {@link Peer#_fillPeerSlots} redials the same candidate.
* Immediate requeue plus `connections:close` was a tight ECONNREFUSED loop.
*/
const PEER_CANDIDATE_RETRY_MS = 60000;
/** Max wire-hash dedup entries in {@link Peer} (bounded memory). */
const PEER_MAX_WIRE_HASH_CACHE = 10000;
/**
Expand Down Expand Up @@ -226,6 +232,11 @@ const CHAT_MAX_RELAYS_PER_ORIGIN_PER_MINUTE = 30;
const PEER_MAX_PENDING_SEALED_DELIVERIES = 32;
/** Max private DocumentRequest reverse-route entries. */
const PEER_MAX_DOCUMENT_RELAY_ROUTES = 256;
/**
* Max RFC6902 patch batches retained on {@link Service#history} (FIFO).
* Unbounded history plus full-state commit snapshots OOMed Hub under reconnects.
*/
const SERVICE_COMMIT_HISTORY_MAX = 256;
const P2P_GENERIC = 0x80; // 128 in decimal
const P2P_IDENT_REQUEST = 0x01; // 1, or the identity
const P2P_IDENT_RESPONSE = 0x11;
Expand Down Expand Up @@ -430,6 +441,7 @@ module.exports = {
PEERING_OFFER_MAX_RELAYS_PER_ORIGIN_PER_MINUTE,
PEERING_OFFER_MAX_PAYLOAD_CACHE,
PEER_MAX_CANDIDATES_QUEUE,
PEER_CANDIDATE_RETRY_MS,
PEER_MAX_WIRE_HASH_CACHE,
PEER_MAX_LOGICAL_REGISTER_CACHE,
PEER_SCORE_BODY_HASH_MISMATCH_PENALTY,
Expand All @@ -447,6 +459,7 @@ module.exports = {
CHAT_MAX_RELAYS_PER_ORIGIN_PER_MINUTE,
PEER_MAX_PENDING_SEALED_DELIVERIES,
PEER_MAX_DOCUMENT_RELAY_ROUTES,
SERVICE_COMMIT_HISTORY_MAX,
P2P_IDENT_REQUEST,
P2P_IDENT_RESPONSE,
P2P_CHAIN_SYNC_REQUEST,
Expand Down
17 changes: 11 additions & 6 deletions docs/OUTSTANDING.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
# Outstanding (security-first)
Living queue for this repo. Detail and closed items live in [SECURITY.md](../SECURITY.md) and [AUDIT.md](../AUDIT.md). Suite production march: [PRODUCTION_MARCH.md](PRODUCTION_MARCH.md).

**Last reviewed:** 2026-08-14. Codacy `500000000` rewritten as `Number('500000000')`; genesis journal cap and publish-author fail-closed on existing files.
**Last reviewed:** 2026-08-14. [#185](https://github.com/FabricLabs/fabric/pull/185) + RC1 first-tier contract (`tests/rc1.first-tier.contract.js`; undersize-frame drop).

## Blockers before shared-host / non-experimental tag
1. **Third-party review** of `types/peer.js`, inventory HTLC, sealed exchange, `publishedDocumentEnvelope` ([AUDIT.md](../AUDIT.md) recommendations).
2. **Do not claim** a hardened contract VM — `Machine.define` still binds host JS ([PUBLIC_API.md](../PUBLIC_API.md)).
3. Downstream **site-login / device-link redeem** is not a core bug; Hub/`@fabric/http` still treat QR `sessionId` as the capability. Tracked in those repos.

## Next slices (this repo)
- [ ] **IdentityCrossSign lift (local, not #183)** — `functions/identityCrossSign.js`, `fabricIdentitySchnorr.js`, `identityCrossSignVerify.js` + tests exist on disk. Held off [#183](https://github.com/FabricLabs/fabric/pull/183) (CodeRabbit ~150-file cap). Do **not** commit working-tree `package.json` / `PUBLIC_API.md` / `AGENTS.md` pointers until that follow-up PR (smoke would require missing leaves). Pin/link core before Hub/http/Passport/GoonCitizen re-exports CI.
- [ ] Type-tree keep/remove lock in [PRODUCTION_MARCH.md](PRODUCTION_MARCH.md) (`Scribe` / `Reader` / Global docs clutter).
- [ ] Eager `messageHex` laziness on Peer hot paths (performance, not a correctness claim).
- [ ] Type-tree keep/remove lock in [PRODUCTION_MARCH.md](PRODUCTION_MARCH.md) (inventory remaining classes; `Scribe`/`Reader` deprecation notes are in).
- [ ] Coordinated `contractId` → `contractIdentifier` rename.
- [ ] Hub / Passport callers still hard-coding BIP44 **7778** on mainnet should use `fabricIdentityDerivationPath(…, network)` (**7777** mainnet / **7778** otherwise).
- [ ] Hub / Passport callers still hard-coding BIP44 **7778** on mainnet should use `fabricIdentityDerivationPath(…, network)` (**7777** mainnet / **7778** otherwise). Do not silently re-derive existing Hub identities.
- [ ] Blinded-execution remains a **scaffold** (not Yao GC) even with signed `at`.
- [ ] Keep `.codacy.yml` Semgrep/Opengrep exclusions for `functions/fabricSetup.js` and `types/environment.js` unless a replacement SAST job covers those path-construction helpers (CodeRabbit asked to drop the excludes; Codacy still ignores `nosemgrep`).

## Closed this pass (do not re-open)
- Undersize AMP frames (`< HEADER_SIZE`) and unparseable buffers drop before parse/crypto (no score / ban). Oversized already did. Locked in `tests/rc1.first-tier.contract.js` with the other first-tier RC1 invariants (hop=0 does not poison gossip cache; BASE_MESSAGE cannot inject gossip; budget is TCP-origin keyed; score-0 still disconnects; sealed wallet public object omits seed/xprv).
- Fabric TUI shoutbox uses UTF-8 `P2P_CHAT_MESSAGE` + `peerAlias` (same mesh as Hub UI / GoonCitizen). Helper `functions/fabricChatText`.
- IdentityCrossSign / identity Schnorr / verify lifted into `functions/` after [#183](https://github.com/FabricLabs/fabric/pull/183) merge (was held for the CodeRabbit file cap).
- `signCrossSign` localPubkey is the Fabric signing pubkey (not Bech32 `id` / HD master). Canonical cross-sign pubkeys are compressed or x-only hex; unknown `kind` is rejected. `fabricIdentityIdFromPubkeyHex` requires compressed 66-hex (no truncated `Buffer.from(..., 'hex')`). `createdAt` is unsigned display metadata. `.d.ts` files declare real arities / `ok` unions.
- `_fillPeerSlots` candidate retry cooldown + NOISE-after-TCP-connect (playnet `:7778` ECONNREFUSED / MaxListeners storm). `_connect` also skips in-flight `_outboundDialTargets`. Dial keys are canonical `host:port` so `pubkey@host:port` cannot duplicate `host:port`. Node URL IPv6 hostnames keep brackets — strip one pair before `createConnection` so `[::1]:port` does not become `[[::1]]:port`. `_candidateRetryAt` is pruned and capped to `maxCandidates`; `_disconnect` and inbound encrypt-end / banned-static paths tear down NOISE; outbound connect setup is try/caught.
- Wallet tmp files use `pid` + random suffix; Environment tests share `tests/helpers/isolatedHome.js`.
- `contract:message` `messageHex` is lazy (getter). `Reader` is `@deprecated` (fold into Peer/Message ingest).
- Blinded-execution `at` bind; Beacon `ready` finalize; peering self-suppress via verified AMP signer; empty `signers: []` spend-key widen; Beacon ARC authority walk.
- Gossip-network `P2P_PEERING_OFFER` candidate expect includes AMP `verifiedPubkey` (`tests/fabric.peer.gossip-network.js`).
- Codacy `no-loss-of-precision` on `functions/contractTaproot.js` `CLTV_TIMESTAMP_THRESHOLD` — use `Number('500000000')` (decimal and `5e8` both trip the PR check).
Expand All @@ -26,4 +31,4 @@ Living queue for this repo. Detail and closed items live in [SECURITY.md](../SEC
- `printGeneratedWallet` never prints the master xprv (`FABRIC_DEBUG` included). Plaintext restore validates encryption password before `setWallet`.

## PRs
[#183](https://github.com/FabricLabs/fabric/pull/183) — mocha + codecov green at `4d7351ee3`; remaining GitHub check was Codacy (`500000000` literal). Cursor review on `4d7351ee3` reports **no remaining High/Medium/Critical**. Still deferred: IdentityCrossSign lift (file cap); `sealedBlob`/`identityLock` `.d.ts` stubs (new files); RFC6902 multi-op JSON bridge (http); eager `messageHex`; `withIsolatedHome` test helper; wallet tmp-name random suffix; `.codacy.yml` Semgrep excludes stay until a replacement SAST job exists.
[#185](https://github.com/FabricLabs/fabric/pull/185) — WIP ARC polish on `feature/rsi` after [#183](https://github.com/FabricLabs/fabric/pull/183) merge. CI green on `488a87da1`. Bugbot Highs (wrong cross-sign pubkey; duplicate outbound dials) addressed; `pubkey@host:port` now canonicalizes onto the same dial slot. Still deferred: RFC6902 multi-op JSON bridge (http); `.codacy.yml` Semgrep excludes stay until a replacement SAST job exists; docstring-coverage gate; PR title.
3 changes: 2 additions & 1 deletion docs/PRODUCTION_MARCH.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Related docs:
- `docs/PRODUCTION.md`
- `docs/RELEASE_CHECKLIST.md`
- `docs/OUTSTANDING.md` — **security-first queue** (this repo)
- `docs/TYPES_AND_SERVICES.md` — cross-package `types/` + `services/` homes (http / Hub / Passport / GoonCitizen)
- `SECURITY.md` / `AUDIT.md`
- `AGENTS.md`

Expand Down Expand Up @@ -84,7 +85,7 @@ Rationale:

### Step B: Fast reductions

- [ ] Mark `Scribe` and `Reader` with explicit deprecation notes in code comments/docs.
- [x] Mark `Scribe` and `Reader` with explicit deprecation notes in code comments/docs.
- [ ] Stop advertising non-core classes in onboarding docs/examples.
- [ ] Keep runtime compatibility for one release cycle through aliasing only where needed.

Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ One-off reports and analysis files in the repo root are listed in **[NON_CANONIC

## Downstream

**hub.fabric.pub** (rendezvous hub, HTTP bridge) and **@fabric/http** are separate repos; align versions per **[docs/PRODUCTION.md](PRODUCTION.md)** and **[../DEVELOPERS.md](../DEVELOPERS.md)**.
**hub.fabric.pub** (rendezvous hub, HTTP bridge) and **@fabric/http** are separate repos; align versions per **[docs/PRODUCTION.md](PRODUCTION.md)** and **[../DEVELOPERS.md](../DEVELOPERS.md)**. Cross-package `types/` + `services/` ownership (including Passport and GoonCitizen): **[TYPES_AND_SERVICES.md](TYPES_AND_SERVICES.md)**.
Loading
Loading