Updates
This commit is contained in:
@@ -1,33 +1,92 @@
|
||||
# hyper-p2p-blind-pair-handoff
|
||||
|
||||
Production module: Blind pair handoff.
|
||||
Three-phase blind session handoff state machine (offer → accept → complete) synced over gossip.
|
||||
|
||||
**Protocol:** `blind-pair-handoff/v1`
|
||||
**Category:** trust-security · **Protocol:** `blind-pair-handoff/v1` · **Exports:** `HyperP2PBlindPairHandoff`, `PROTOCOL`
|
||||
|
||||
## When to use
|
||||
|
||||
Peer session migration.
|
||||
Coordinate moving a session to `targetPeer` without exposing pair details in this module.
|
||||
|
||||
## When not to use
|
||||
|
||||
Direct connect only.
|
||||
Instant local pairing only (`secret-stream-pair`).
|
||||
|
||||
## Quick start
|
||||
|
||||
```js
|
||||
const { HyperP2PBlindPairHandoff } = require('hyper-p2p-blind-pair-handoff')
|
||||
const m = new HyperP2PBlindPairHandoff()
|
||||
await m.ready()
|
||||
await m.close()
|
||||
const h = new HyperP2PBlindPairHandoff({ topic: 'handoff' })
|
||||
await h.ready()
|
||||
h.offerHandoff('sess-1', 'target-peer-hex')
|
||||
h.acceptHandoff('sess-1')
|
||||
h.completeHandoff('sess-1')
|
||||
await h.close()
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [docs/api.md](docs/api.md)
|
||||
- [docs/architecture.md](docs/architecture.md)
|
||||
- [docs/api.md](docs/api.md) · [docs/architecture.md](docs/architecture.md)
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
npm install && npm test
|
||||
npm test
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Member | Description |
|
||||
|--------|-------------|
|
||||
| `new HyperP2PBlindPairHandoff(opts?)` | `topic`, `keyPair`. |
|
||||
| `offerHandoff(sessionId, targetPeer)` | State `offered`; gossips `handoff-offer`. |
|
||||
| `acceptHandoff(sessionId)` | Requires `offered`; state `accepted`. |
|
||||
| `completeHandoff(sessionId)` | Requires `accepted`; state `completed`. |
|
||||
| `getHandoff(sessionId)` | Full record or `null`. |
|
||||
| `getStats()` | `{ offered, accepted, completed, gossipIn, gossipOut, handoffs, protocol }`. |
|
||||
| `ready()` / `close()` | Swarm lifecycle. |
|
||||
|
||||
**Events:** `offered`, `accepted`, `completed`, `closed`. Throws if wrong state or missing handoff.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
_handoffs Map(sessionId -> { sessionId, targetPeer, state, offeredAt, acceptedAt, completedAt })
|
||||
Remote: offer creates if missing; accept/complete advance only from valid prior state
|
||||
```
|
||||
|
||||
Gossip does not emit events on remote—only updates map (listen via polling `getHandoff` or extend locally).
|
||||
|
||||
## Wire table
|
||||
|
||||
| `type` | Fields | Direction |
|
||||
|--------|--------|-----------|
|
||||
| `handoff-offer` | `sessionId`, `targetPeer`, `offeredAt` | Any → all |
|
||||
| `handoff-accept` | `sessionId`, `acceptedAt` | Any → all |
|
||||
| `handoff-complete` | `sessionId`, `completedAt` | Any → all |
|
||||
|
||||
## Errors
|
||||
|
||||
- `acceptHandoff` / `completeHandoff`: missing id → `handoff not found`; wrong state → `handoff not offered` / `not accepted`.
|
||||
- Empty `sessionId` / `targetPeer` on offer.
|
||||
|
||||
## Composition
|
||||
|
||||
- `sessionId` aligns with `hyper-p2p-noise-session-wrap` ids.
|
||||
- `targetPeer` is hex or app-defined peer string.
|
||||
- Use after `hyper-p2p-blind-relay-bridge` selects a relay path.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const { HyperP2PBlindPairHandoff } = require('hyper-p2p-blind-pair-handoff')
|
||||
|
||||
const h = new HyperP2PBlindPairHandoff({ topic: process.argv[2] })
|
||||
await h.ready()
|
||||
|
||||
h.offerHandoff('sess-1', 'target-peer-hex')
|
||||
h.acceptHandoff('sess-1')
|
||||
h.completeHandoff('sess-1')
|
||||
console.log(h.getHandoff('sess-1'))
|
||||
await h.close()
|
||||
```
|
||||
|
||||
@@ -1,26 +1,97 @@
|
||||
# API: hyper-p2p-blind-pair-handoff
|
||||
|
||||
**Protocol:** `blind-pair-handoff/v1` · **Export:** `HyperP2PBlindPairHandoff`
|
||||
**Protocol:** `blind-pair-handoff/v1` · **Export:** `HyperP2PBlindPairHandoff`, `PROTOCOL`
|
||||
|
||||
## Overview
|
||||
|
||||
Coordinates blind session handoffs through offered → accepted → completed states. Gossips state transitions so peers observe handoff progress without exposing full pairing material in the API surface.
|
||||
|
||||
## Constructor
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `topic` | `string` \| `Buffer` | `null` | Swarm topic |
|
||||
| `keyPair` | `KeyPair` | random | Local identity |
|
||||
|
||||
## Methods
|
||||
|
||||
### `offerHandoff(...)`
|
||||
### `offerHandoff(sessionId, targetPeer)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** handoff `{ sessionId, targetPeer, state: 'offered', offeredAt, acceptedAt: null, completedAt: null }`
|
||||
- **Throws:** `assertNonEmpty` on `sessionId`, `targetPeer`
|
||||
- **Gossip:** `{ type: 'handoff-offer', sessionId, targetPeer, offeredAt }`
|
||||
- **Emits:** `offered`
|
||||
|
||||
### `acceptHandoff(...)`
|
||||
### `acceptHandoff(sessionId)`
|
||||
|
||||
Domain API.
|
||||
- **Throws:** `handoff not found: ${sessionId}`; `handoff not offered: ${sessionId}`
|
||||
- **Gossip:** `{ type: 'handoff-accept', sessionId, acceptedAt }`
|
||||
- **Emits:** `accepted`
|
||||
|
||||
### `completeHandoff(...)`
|
||||
### `completeHandoff(sessionId)`
|
||||
|
||||
Domain API.
|
||||
- **Throws:** `handoff not found: ${sessionId}`; `handoff not accepted: ${sessionId}`
|
||||
- **Gossip:** `{ type: 'handoff-complete', sessionId, completedAt }`
|
||||
- **Emits:** `completed`
|
||||
|
||||
### `getHandoff(sessionId)`
|
||||
|
||||
### `getStats()` / `ready()` / `close()`
|
||||
- **Returns:** handoff or `null`
|
||||
|
||||
Lifecycle helpers.
|
||||
### `ready()` / `close()`
|
||||
|
||||
Standard swarm lifecycle.
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Payload |
|
||||
|-------|---------|
|
||||
| `offered` | handoff object |
|
||||
| `accepted` | handoff object |
|
||||
| `completed` | handoff object |
|
||||
| `closed` | — |
|
||||
|
||||
## getStats()
|
||||
|
||||
| Field | Meaning |
|
||||
|-------|---------|
|
||||
| `offered` / `accepted` / `completed` | transition counts |
|
||||
| `gossipIn` / `gossipOut` | mesh |
|
||||
| `handoffs` | map size |
|
||||
| `protocol` | `blind-pair-handoff/v1` |
|
||||
|
||||
## Wire
|
||||
|
||||
| type | fields | behavior |
|
||||
|------|--------|----------|
|
||||
| `handoff-offer` | `sessionId`, `targetPeer`, `offeredAt` | Create offered entry if missing |
|
||||
| `handoff-accept` | `sessionId`, `acceptedAt` | Promote offered → accepted |
|
||||
| `handoff-complete` | `sessionId`, `completedAt` | Promote accepted → completed |
|
||||
|
||||
## Errors
|
||||
|
||||
State machine errors listed above. Empty ids via `assertNonEmpty`.
|
||||
|
||||
See [`../../_shared/ERROR_CODES.md`](../../_shared/ERROR_CODES.md).
|
||||
|
||||
## P2P
|
||||
|
||||
Joins Hyperswarm when `topic` is set.
|
||||
Remote handlers in `_onGossip` merge offers and advance state when local state allows.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cd modules/trust-security/hyper-p2p-blind-pair-handoff && npm test
|
||||
```
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-blind-relay-bridge`, `hyper-p2p-secret-stream-pair`, `hyper-p2p-noise-session-wrap`.
|
||||
|
||||
## Example
|
||||
|
||||
See [`examples/basic.js`](../examples/basic.js).
|
||||
|
||||
## See also
|
||||
|
||||
[`docs/architecture.md`](architecture.md).
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
# Architecture: hyper-p2p-blind-pair-handoff
|
||||
|
||||
**Protocol:** `blind-pair-handoff/v1` · **Category:** trust-security
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> offered
|
||||
offered --> accepted
|
||||
accepted --> completed
|
||||
```
|
||||
|
||||
## Wire messages
|
||||
|
||||
| type | fields | direction | behavior |
|
||||
|------|--------|-----------|----------|
|
||||
| `handoff-offer` | sessionId, targetPeer | gossip | Offer |
|
||||
| `handoff-accept` | sessionId | gossip | Accept |
|
||||
| `handoff-complete` | sessionId | gossip | Done |
|
||||
| type | direction | fields | behavior |
|
||||
|------|-----------|--------|----------|
|
||||
| `handoff-offer` | gossip | `sessionId`, `targetPeer`, `offeredAt` | Insert if `sessionId` unknown |
|
||||
| `handoff-accept` | gossip | `sessionId`, `acceptedAt` | Set state accepted when offered |
|
||||
| `handoff-complete` | gossip | `sessionId`, `completedAt` | Set state completed when accepted |
|
||||
|
||||
## State
|
||||
## State model
|
||||
|
||||
In-memory structures; gossip via `initModuleSwarm` / `gossipSend` when P2P enabled.
|
||||
`_handoffs`: Map sessionId → handoff record with `state` enum.
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-blind-relay-bridge`, `hyper-p2p-secret-stream-pair`.
|
||||
|
||||
@@ -1,34 +1,92 @@
|
||||
# hyper-p2p-encrypted-topic
|
||||
|
||||
Production module: Topic encryption demo.
|
||||
Register topics with a `keyHint`, derive a symmetric key, XOR-encrypt payloads; gossip topic registration (hint only).
|
||||
|
||||
**Protocol:** `encrypted-topic/v1`
|
||||
**Category:** trust-security · **Protocol:** `encrypted-topic/v1` · **Exports:** `HyperP2PEncryptedTopic`, `PROTOCOL`, `deriveKey`, `xorCrypt`
|
||||
|
||||
## When to use
|
||||
|
||||
Topic-scoped payload crypto.
|
||||
Lightweight obfuscation keyed by shared hint on a mesh (not a substitute for Noise).
|
||||
|
||||
## When not to use
|
||||
|
||||
Production AEAD.
|
||||
Production confidentiality without real AEAD/Noise.
|
||||
|
||||
## Quick start
|
||||
|
||||
```js
|
||||
const { HyperP2PEncryptedTopic } = require('hyper-p2p-encrypted-topic')
|
||||
const m = new HyperP2PEncryptedTopic()
|
||||
m.registerTopic('t','hint')
|
||||
await m.ready()
|
||||
await m.close()
|
||||
const enc = new HyperP2PEncryptedTopic({ topic: 'secure' })
|
||||
await enc.ready()
|
||||
enc.registerTopic('private-feed', 'shared-secret-hint')
|
||||
const cipher = enc.encryptPayload('private-feed', 'hello')
|
||||
console.log(enc.decryptPayload('private-feed', cipher).toString())
|
||||
await enc.close()
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [docs/api.md](docs/api.md)
|
||||
- [docs/architecture.md](docs/architecture.md)
|
||||
- [docs/api.md](docs/api.md) · [docs/architecture.md](docs/architecture.md)
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
npm install && npm test
|
||||
npm test
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Member | Description |
|
||||
|--------|-------------|
|
||||
| `new HyperP2PEncryptedTopic(opts?)` | `topic`, `keyPair`. |
|
||||
| `registerTopic(topicId, keyHint)` | `key = hash(keyHint)`; gossips `topic-register`. |
|
||||
| `encryptPayload(topicId, data)` | Buffer or encodable; returns cipher buffer. |
|
||||
| `decryptPayload(topicId, buf)` | XOR decrypt; requires registered topic. |
|
||||
| `deriveKey(hint)` / `xorCrypt(keyBuf, dataBuf)` | Low-level helpers (exported). |
|
||||
| `getStats()` | `{ registered, encrypted, decrypted, gossipIn, gossipOut, topics, protocol }`. |
|
||||
| `ready()` / `close()` | Swarm lifecycle. |
|
||||
|
||||
**Events:** `registered`, `closed`. Remote register fills map if `topicId` new.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
_topics Map(topicId -> { keyHint, key, registeredAt })
|
||||
deriveKey: hypercore-crypto.hash(b4a.from(String(hint)))
|
||||
xorCrypt: repeating-key XOR byte-wise
|
||||
```
|
||||
|
||||
Peers must use same `keyHint` for a `topicId` to decrypt. Gossip shares hint, not key bytes.
|
||||
|
||||
## Wire table
|
||||
|
||||
| `type` | Fields | Direction |
|
||||
|--------|--------|-----------|
|
||||
| `topic-register` | `topicId`, `keyHint` | Any → all |
|
||||
|
||||
## Errors
|
||||
|
||||
- Unregistered `topicId` on encrypt/decrypt throws `topic not registered`.
|
||||
- `decryptPayload`: non-buffer input throws.
|
||||
- Empty `topicId` / `keyHint` on register.
|
||||
|
||||
## Composition
|
||||
|
||||
- XOR is not AEAD—layer Noise or app crypto for real secrecy.
|
||||
- Same `keyHint` across peers required; rotate via `hyper-p2p-key-rotation` on hint strings.
|
||||
- Gossip does not re-broadcast ciphertext—only registration.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const { HyperP2PEncryptedTopic } = require('hyper-p2p-encrypted-topic')
|
||||
|
||||
const enc = new HyperP2PEncryptedTopic({ topic: process.argv[2] })
|
||||
await enc.ready()
|
||||
|
||||
enc.registerTopic('private-feed', 'shared-secret-hint')
|
||||
const cipher = enc.encryptPayload('private-feed', 'hello')
|
||||
const plain = enc.decryptPayload('private-feed', cipher)
|
||||
console.log(plain.toString())
|
||||
await enc.close()
|
||||
```
|
||||
|
||||
@@ -1,26 +1,91 @@
|
||||
# API: hyper-p2p-encrypted-topic
|
||||
|
||||
**Protocol:** `encrypted-topic/v1` · **Export:** `HyperP2PEncryptedTopic`
|
||||
**Protocol:** `encrypted-topic/v1` · **Export:** `HyperP2PEncryptedTopic`, `PROTOCOL`, `deriveKey`, `xorCrypt`
|
||||
|
||||
## Overview
|
||||
|
||||
Registers logical topics with a `keyHint`, derives a symmetric key via `hypercore-crypto.hash`, and XOR-encrypts payloads. Gossips `topic-register` so peers learn hints (not raw keys — key re-derived locally).
|
||||
|
||||
## Constructor
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `topic` | `string` \| `Buffer` | `null` | Swarm topic |
|
||||
| `keyPair` | `KeyPair` | random | Swarm identity |
|
||||
|
||||
## Methods
|
||||
|
||||
### `registerTopic(...)`
|
||||
### `registerTopic(topicId, keyHint)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** `{ topicId, keyHint, key, registeredAt }`
|
||||
- **Throws:** `assertNonEmpty` on both args
|
||||
- **Gossip:** `{ type: 'topic-register', topicId, keyHint }`
|
||||
- **Emits:** `registered` `{ topicId, keyHint }`
|
||||
|
||||
### `encryptPayload(...)`
|
||||
### `encryptPayload(topicId, data)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** Buffer cipher
|
||||
- **Throws:** `topic not registered: ${topicId}`
|
||||
|
||||
### `decryptPayload(...)`
|
||||
### `decryptPayload(topicId, buf)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** plain Buffer
|
||||
- **Throws:** `buf must be a buffer`; `topic not registered: ${topicId}`
|
||||
|
||||
### `ready()` / `close()`
|
||||
|
||||
### `getStats()` / `ready()` / `close()`
|
||||
Standard swarm.
|
||||
|
||||
Lifecycle helpers.
|
||||
## Events
|
||||
|
||||
| Event | Payload |
|
||||
|-------|---------|
|
||||
| `registered` | `{ topicId, keyHint }` |
|
||||
| `closed` | — |
|
||||
|
||||
## getStats()
|
||||
|
||||
`registered`, `encrypted`, `decrypted`, `gossipIn`, `gossipOut`, `topics`, `protocol`.
|
||||
|
||||
## Wire
|
||||
|
||||
| type | fields | behavior |
|
||||
|------|--------|----------|
|
||||
| `topic-register` | `topicId`, `keyHint` | Insert topic if unknown; derive key |
|
||||
|
||||
## Errors
|
||||
|
||||
Registration and buffer errors above. Uses `assertNonEmpty` from shared errors.
|
||||
|
||||
See [`../../_shared/ERROR_CODES.md`](../../_shared/ERROR_CODES.md).
|
||||
|
||||
## P2P
|
||||
|
||||
Joins Hyperswarm when `topic` is set.
|
||||
XOR is not authenticated encryption — use for obfuscation/lightweight topic privacy only.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cd modules/trust-security/hyper-p2p-encrypted-topic && npm test
|
||||
```
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-topic-announcer`, `hyper-p2p-key-rotation`, `hyper-p2p-session-rotation`.
|
||||
|
||||
## Example
|
||||
|
||||
See [`examples/basic.js`](../examples/basic.js).
|
||||
|
||||
## Remote merge rules
|
||||
|
||||
- `topic-register` only inserts when `topicId` not already local
|
||||
- Remote peers re-derive `key` from gossiped `keyHint` (never send raw key)
|
||||
|
||||
## Lifecycle
|
||||
|
||||
Encrypt/decrypt increment `encrypted` / `decrypted` stats per operation.
|
||||
|
||||
## See also
|
||||
|
||||
[`docs/architecture.md`](architecture.md), [`../../MODULE_CATEGORIES.md`](../../MODULE_CATEGORIES.md).
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
# Architecture: hyper-p2p-encrypted-topic
|
||||
|
||||
**Protocol:** `encrypted-topic/v1` · **Category:** trust-security
|
||||
|
||||
## Wire messages
|
||||
|
||||
| type | fields | direction | behavior |
|
||||
|------|--------|-----------|----------|
|
||||
| `topic-register` | topicId, keyHint | gossip | Register |
|
||||
| type | direction | fields | behavior |
|
||||
|------|-----------|--------|----------|
|
||||
| `topic-register` | gossip | `topicId`, `keyHint` | Store topic; `deriveKey(keyHint)` locally |
|
||||
|
||||
## State
|
||||
## State model
|
||||
|
||||
In-memory structures; gossip via `initModuleSwarm` / `gossipSend` when P2P enabled.
|
||||
`_topics`: Map topicId → `{ topicId, keyHint, key, registeredAt }`.
|
||||
|
||||
## Helpers
|
||||
|
||||
- `deriveKey(hint)` → `crypto.hash(b4a.from(String(hint)))`
|
||||
- `xorCrypt(keyBuf, dataBuf)` — byte XOR stream cipher
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-key-rotation`, `hyper-p2p-topic-announcer`.
|
||||
|
||||
@@ -1,34 +1,89 @@
|
||||
# hyper-p2p-key-rotation
|
||||
|
||||
Production module: Key rotation scheduling.
|
||||
Schedule and activate key material rotations by `keyId` with gossip sync and time-based activation.
|
||||
|
||||
**Protocol:** `key-rotation/v1`
|
||||
**Category:** trust-security · **Protocol:** `key-rotation/v1` · **Exports:** `HyperP2PKeyRotation`, `PROTOCOL`
|
||||
|
||||
## When to use
|
||||
|
||||
Rotating keys on a timeline.
|
||||
Rotate logical keys on a clock across peers (`activateAt`).
|
||||
|
||||
## When not to use
|
||||
|
||||
Static keys only.
|
||||
Hypercore key rotation built into core replication.
|
||||
|
||||
## Quick start
|
||||
|
||||
```js
|
||||
const { HyperP2PKeyRotation } = require('hyper-p2p-key-rotation')
|
||||
const m = new HyperP2PKeyRotation()
|
||||
m.scheduleRotation('k', 'mat', Date.now())
|
||||
await m.ready()
|
||||
await m.close()
|
||||
const rot = new HyperP2PKeyRotation({ topic: 'keys' })
|
||||
await rot.ready()
|
||||
rot.scheduleRotation('app-key', 'material-v2', Date.now() + 3600_000)
|
||||
console.log(rot.activeKey('app-key'))
|
||||
await rot.close()
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [docs/api.md](docs/api.md)
|
||||
- [docs/architecture.md](docs/architecture.md)
|
||||
- [docs/api.md](docs/api.md) · [docs/architecture.md](docs/architecture.md)
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
npm install && npm test
|
||||
npm test
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Member | Description |
|
||||
|--------|-------------|
|
||||
| `new HyperP2PKeyRotation(opts?)` | `topic`, `keyPair`. |
|
||||
| `scheduleRotation(keyId, newMaterial, activateAt?)` | Default `activateAt = now`; may activate immediately. |
|
||||
| `activeKey(keyId)` | `{ keyId, material, activatedAt }` or `null` (runs pending check). |
|
||||
| `pendingRotations()` | Future `{ keyId, newMaterial, activateAt, scheduledAt }[]`. |
|
||||
| `getStats()` | `{ scheduled, activated, gossipIn, gossipOut, activeKeys, pending, protocol }`. |
|
||||
| `ready()` / `close()` | Swarm lifecycle. |
|
||||
|
||||
**Events:** `scheduled`, `activated`, `closed`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
_pending Map(keyId -> [entries])
|
||||
_active Map(keyId -> { material, activatedAt })
|
||||
_maybeActivate: all entries with activateAt <= now move to active + gossip rotation-activated
|
||||
```
|
||||
|
||||
`newMaterial` is opaque string (app-defined encoding).
|
||||
|
||||
## Wire table
|
||||
|
||||
| `type` | Fields | Direction |
|
||||
|--------|--------|-----------|
|
||||
| `rotation-scheduled` | `keyId`, `newMaterial`, `activateAt` | Any → all |
|
||||
| `rotation-activated` | `keyId`, `material`, `activatedAt` | Any → all |
|
||||
|
||||
## Errors
|
||||
|
||||
- Empty `keyId` or `newMaterial` on schedule / `activeKey`.
|
||||
- Pending list only includes future `activateAt` entries.
|
||||
|
||||
## Composition
|
||||
|
||||
- Store serialized keys in `newMaterial` (JSON string, hex, etc.).
|
||||
- Call `activeKey` before encrypt ops in `hyper-p2p-encrypted-topic`.
|
||||
- Immediate activation when `activateAt <= now` on schedule.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const { HyperP2PKeyRotation } = require('hyper-p2p-key-rotation')
|
||||
|
||||
const rot = new HyperP2PKeyRotation({ topic: process.argv[2] })
|
||||
await rot.ready()
|
||||
|
||||
rot.scheduleRotation('app-key', 'material-v2', Date.now() + 3600_000)
|
||||
console.log(rot.activeKey('app-key'))
|
||||
console.log(rot.pendingRotations())
|
||||
await rot.close()
|
||||
```
|
||||
|
||||
@@ -1,26 +1,93 @@
|
||||
# API: hyper-p2p-key-rotation
|
||||
|
||||
**Protocol:** `key-rotation/v1` · **Export:** `HyperP2PKeyRotation`
|
||||
**Protocol:** `key-rotation/v1` · **Export:** `HyperP2PKeyRotation`, `PROTOCOL`
|
||||
|
||||
## Overview
|
||||
|
||||
Schedules key material rotations with optional `activateAt` timestamp and activates when due. Gossips schedule and activation events for mesh-wide key state.
|
||||
|
||||
## Constructor
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `topic` | `string` \| `Buffer` | `null` | Swarm topic |
|
||||
| `keyPair` | `KeyPair` | random | Identity |
|
||||
|
||||
## Methods
|
||||
|
||||
### `scheduleRotation(...)`
|
||||
### `scheduleRotation(keyId, newMaterial, activateAt?)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** `{ keyId, newMaterial, activateAt, scheduledAt }`
|
||||
- **Throws:** `assertNonEmpty` on `keyId`, `newMaterial`
|
||||
- **Gossip:** `{ type: 'rotation-scheduled', keyId, newMaterial, activateAt }`
|
||||
- **Emits:** `scheduled`
|
||||
- **Side effect:** `_maybeActivate(keyId)` immediately
|
||||
|
||||
### `activeKey(...)`
|
||||
### `activeKey(keyId)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** `{ keyId, material, activatedAt }` or `null`
|
||||
- Runs `_maybeActivate` before read
|
||||
|
||||
### `pendingRotations(...)`
|
||||
### `pendingRotations()`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** array of future rotations (`activateAt > now`)
|
||||
|
||||
### `ready()` / `close()`
|
||||
|
||||
### `getStats()` / `ready()` / `close()`
|
||||
Standard.
|
||||
|
||||
Lifecycle helpers.
|
||||
## Events
|
||||
|
||||
| Event | Payload |
|
||||
|-------|---------|
|
||||
| `scheduled` | schedule entry |
|
||||
| `activated` | active key record |
|
||||
| `closed` | — |
|
||||
|
||||
## getStats()
|
||||
|
||||
`scheduled`, `activated`, `gossipIn`, `gossipOut`, `activeKeys`, `pending`, `protocol`.
|
||||
|
||||
## Wire
|
||||
|
||||
| type | fields | behavior |
|
||||
|------|--------|----------|
|
||||
| `rotation-scheduled` | `keyId`, `newMaterial`, `activateAt` | Append pending; maybe activate |
|
||||
| `rotation-activated` | `keyId`, `material`, `activatedAt` | Set `_active` |
|
||||
|
||||
## Errors
|
||||
|
||||
`assertNonEmpty` on key ids.
|
||||
|
||||
See [`../../_shared/ERROR_CODES.md`](../../_shared/ERROR_CODES.md).
|
||||
|
||||
## P2P
|
||||
|
||||
Joins Hyperswarm when `topic` is set.
|
||||
Activation is time-based (`activateAt <= Date.now()`).
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cd modules/trust-security/hyper-p2p-key-rotation && npm test
|
||||
```
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-encrypted-topic`, `hyper-p2p-session-rotation`, `hyper-p2p-noise-session-wrap`.
|
||||
|
||||
## Example
|
||||
|
||||
See [`examples/basic.js`](../examples/basic.js).
|
||||
|
||||
## Remote merge rules
|
||||
|
||||
- `rotation-scheduled` appends to pending list then `_maybeActivate`
|
||||
- `rotation-activated` overwrites `_active` for `keyId`
|
||||
|
||||
## Lifecycle
|
||||
|
||||
Call `activeKey` or `scheduleRotation` to trigger time-based activation checks.
|
||||
|
||||
## See also
|
||||
|
||||
[`docs/architecture.md`](architecture.md), [`../../MODULE_CATEGORIES.md`](../../MODULE_CATEGORIES.md).
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
# Architecture: hyper-p2p-key-rotation
|
||||
|
||||
**Protocol:** `key-rotation/v1` · **Category:** trust-security
|
||||
|
||||
## Wire messages
|
||||
|
||||
| type | fields | direction | behavior |
|
||||
|------|--------|-----------|----------|
|
||||
| `rotation-scheduled` | keyId, newMaterial, activateAt | gossip | Schedule |
|
||||
| `rotation-activated` | keyId, material, activatedAt | gossip | Activate |
|
||||
| type | direction | fields | behavior |
|
||||
|------|-----------|--------|----------|
|
||||
| `rotation-scheduled` | gossip | `keyId`, `newMaterial`, `activateAt` | Queue pending; `_maybeActivate` |
|
||||
| `rotation-activated` | gossip | `keyId`, `material`, `activatedAt` | Write `_active` map |
|
||||
|
||||
## State
|
||||
## State model
|
||||
|
||||
In-memory structures; gossip via `initModuleSwarm` / `gossipSend` when P2P enabled.
|
||||
- `_active`: Map keyId → active material
|
||||
- `_pending`: Map keyId → array of scheduled entries
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-encrypted-topic`, `hyper-p2p-session-rotation`.
|
||||
|
||||
@@ -1,33 +1,91 @@
|
||||
# hyper-p2p-multisig-threshold
|
||||
|
||||
Production module: Threshold multisig.
|
||||
Threshold approval proposals: create with signers list, collect signatures, approve when count ≥ threshold.
|
||||
|
||||
**Protocol:** `multisig-threshold/v1`
|
||||
**Category:** trust-security · **Protocol:** `multisig-threshold/v1` · **Exports:** `HyperP2PMultisigThreshold`, `PROTOCOL`
|
||||
|
||||
## When to use
|
||||
|
||||
M-of-N approvals.
|
||||
Gossip-aligned multisig-style gates (ids, signer ids as strings).
|
||||
|
||||
## When not to use
|
||||
|
||||
Single signer.
|
||||
On-chain multisig or Ed25519 aggregate signatures (signatures are presence flags here).
|
||||
|
||||
## Quick start
|
||||
|
||||
```js
|
||||
const { HyperP2PMultisigThreshold } = require('hyper-p2p-multisig-threshold')
|
||||
const m = new HyperP2PMultisigThreshold()
|
||||
await m.ready()
|
||||
await m.close()
|
||||
const ms = new HyperP2PMultisigThreshold({ topic: 'gov' })
|
||||
await ms.ready()
|
||||
ms.createProposal('deploy-1', ['alice', 'bob', 'carol'], 2)
|
||||
ms.addSignature('deploy-1', 'alice')
|
||||
console.log(ms.isApproved('deploy-1'))
|
||||
await ms.close()
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [docs/api.md](docs/api.md)
|
||||
- [docs/architecture.md](docs/architecture.md)
|
||||
- [docs/api.md](docs/api.md) · [docs/architecture.md](docs/architecture.md)
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
npm install && npm test
|
||||
npm test
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Member | Description |
|
||||
|--------|-------------|
|
||||
| `new HyperP2PMultisigThreshold(opts?)` | `topic`, `keyPair`. |
|
||||
| `createProposal(id, signers, threshold)` | `1 ≤ threshold ≤ signers.length`; gossips `proposal-create`. |
|
||||
| `addSignature(id, signer)` | Signer must be in list; returns `{ id, signer, count, approved }`. |
|
||||
| `isApproved(id)` | Boolean. |
|
||||
| `getStats()` | `{ created, signatures, approved, gossipIn, gossipOut, proposals, protocol }`. |
|
||||
| `ready()` / `close()` | Swarm lifecycle. |
|
||||
|
||||
**Events:** `proposal`, `approved`, `closed`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
_proposals Map(id -> { signers[], threshold, signatures Set, approved })
|
||||
Remote sigs add to Set; approved when size >= threshold
|
||||
```
|
||||
|
||||
No cryptographic verify of `signer` string—authorization is membership in `signers`.
|
||||
|
||||
## Wire table
|
||||
|
||||
| `type` | Fields | Direction |
|
||||
|--------|--------|-----------|
|
||||
| `proposal-create` | `id`, `signers`, `threshold`, `createdAt` | Any → all |
|
||||
| `proposal-sig` | `id`, `signer` | Any → all |
|
||||
|
||||
## Errors
|
||||
|
||||
- Invalid `signers` array or `threshold` on `createProposal`.
|
||||
- Unknown proposal or unauthorized `signer` on `addSignature`.
|
||||
|
||||
## Composition
|
||||
|
||||
- Signer ids often match `peerHex` from discovery modules.
|
||||
- Gate deploys with `isApproved` before `hyper-pear-update-gossip` publish.
|
||||
- Duplicate signatures from same signer are idempotent (Set).
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const { HyperP2PMultisigThreshold } = require('hyper-p2p-multisig-threshold')
|
||||
|
||||
const ms = new HyperP2PMultisigThreshold({ topic: process.argv[2] })
|
||||
ms.on('approved', ({ id }) => console.log('approved', id))
|
||||
await ms.ready()
|
||||
|
||||
ms.createProposal('deploy-1', ['alice', 'bob', 'carol'], 2)
|
||||
ms.addSignature('deploy-1', 'alice')
|
||||
ms.addSignature('deploy-1', 'bob')
|
||||
console.log(ms.isApproved('deploy-1'))
|
||||
await ms.close()
|
||||
```
|
||||
|
||||
@@ -1,26 +1,85 @@
|
||||
# API: hyper-p2p-multisig-threshold
|
||||
|
||||
**Protocol:** `multisig-threshold/v1` · **Export:** `HyperP2PMultisigThreshold`
|
||||
**Protocol:** `multisig-threshold/v1` · **Export:** `HyperP2PMultisigThreshold`, `PROTOCOL`
|
||||
|
||||
## Overview
|
||||
|
||||
Threshold signature coordination for proposals: create with signer list and threshold, collect signatures until `threshold` met, gossip create and sig messages.
|
||||
|
||||
## Constructor
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `topic` | `string` \| `Buffer` | `null` | Swarm topic |
|
||||
| `keyPair` | `KeyPair` | random | Identity |
|
||||
|
||||
## Methods
|
||||
|
||||
### `createProposal(...)`
|
||||
### `createProposal(id, signers, threshold)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** `{ id, signers, threshold }`
|
||||
- **Throws:** `signers must be a non-empty array`; `threshold must be between 1 and signers.length`
|
||||
- **Gossip:** `{ type: 'proposal-create', id, signers, threshold, createdAt }`
|
||||
- **Emits:** `proposal`
|
||||
|
||||
### `addSignature(...)`
|
||||
### `addSignature(id, signer)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** `{ id, signer, count, approved }`
|
||||
- **Throws:** `proposal not found: ${id}`; `signer not authorized: ${signer}`
|
||||
- **Gossip:** `{ type: 'proposal-sig', id, signer }`
|
||||
- **Emits:** `approved` when threshold first reached
|
||||
|
||||
### `isApproved(...)`
|
||||
### `isApproved(id)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** boolean
|
||||
|
||||
### `ready()` / `close()`
|
||||
|
||||
### `getStats()` / `ready()` / `close()`
|
||||
Standard.
|
||||
|
||||
Lifecycle helpers.
|
||||
## Events
|
||||
|
||||
| Event | Payload |
|
||||
|-------|---------|
|
||||
| `proposal` | `{ id, signers, threshold }` |
|
||||
| `approved` | `{ id }` |
|
||||
| `closed` | — |
|
||||
|
||||
## getStats()
|
||||
|
||||
`created`, `signatures`, `approved`, `gossipIn`, `gossipOut`, `proposals`, `protocol`.
|
||||
|
||||
## Wire
|
||||
|
||||
| type | fields | behavior |
|
||||
|------|--------|----------|
|
||||
| `proposal-create` | `id`, `signers`, `threshold`, `createdAt` | Insert proposal if missing |
|
||||
| `proposal-sig` | `id`, `signer` | Add sig if signer authorized; maybe approve |
|
||||
|
||||
## Errors
|
||||
|
||||
Validation strings above; `assertNonEmpty` on ids.
|
||||
|
||||
See [`../../_shared/ERROR_CODES.md`](../../_shared/ERROR_CODES.md).
|
||||
|
||||
## P2P
|
||||
|
||||
Joins Hyperswarm when `topic` is set.
|
||||
Signatures stored in `Set` per proposal (not deduplicated across duplicate gossip).
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cd modules/trust-security/hyper-p2p-multisig-threshold && npm test
|
||||
```
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-key-rotation`, agent workflows requiring quorum.
|
||||
|
||||
## Example
|
||||
|
||||
See [`examples/basic.js`](../examples/basic.js).
|
||||
|
||||
## See also
|
||||
|
||||
[`docs/architecture.md`](architecture.md).
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
# Architecture: hyper-p2p-multisig-threshold
|
||||
|
||||
**Protocol:** `multisig-threshold/v1` · **Category:** trust-security
|
||||
|
||||
## Wire messages
|
||||
|
||||
| type | fields | direction | behavior |
|
||||
|------|--------|-----------|----------|
|
||||
| `proposal-create` | id, signers, threshold | gossip | Create |
|
||||
| `proposal-sig` | id, signer | gossip | Sign |
|
||||
| type | direction | fields | behavior |
|
||||
|------|-----------|--------|----------|
|
||||
| `proposal-create` | gossip | `id`, `signers`, `threshold`, `createdAt` | Create `_proposals` entry |
|
||||
| `proposal-sig` | gossip | `id`, `signer` | Add to `signatures` Set; set `approved` at threshold |
|
||||
|
||||
## State
|
||||
## State model
|
||||
|
||||
In-memory structures; gossip via `initModuleSwarm` / `gossipSend` when P2P enabled.
|
||||
`_proposals`: Map id → `{ id, signers, threshold, signatures: Set, createdAt, approved }`.
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-p2p-key-rotation`, `hyper-p2p-workflow-graph`.
|
||||
|
||||
@@ -1,43 +1,103 @@
|
||||
# hyper-p2p-reputation-system
|
||||
|
||||
HyperP2PReputationSystem Novel P2P reputation/trust primitive for Bare/Pear. - Cryptographic Ed25519 signed attestations for tamper-proof updates - Time-decaying scores with configurable rate
|
||||
Ed25519-signed attestations, decaying scores, optional persistence, and gossip for P2P trust on Bare/Pear.
|
||||
|
||||
**Category:** Trust & security
|
||||
|
||||
**Composes with:** `hyper-p2p-attestation-chain`, `hyper-p2p-trust-graph`
|
||||
|
||||
**Protocol:** `hyper-p2p-reputation-system/v1`
|
||||
**Category:** trust-security · **Protocol:** `hyper-p2p-reputation-system/v1` · **Export:** `HyperP2PReputationSystem` (default), `REPUTATION_PROTOCOL`
|
||||
|
||||
## When to use
|
||||
|
||||
Multi-peer apps that need trust & security over a shared Hyperswarm topic.
|
||||
Peer trust scores with tamper-evident deltas and optional `topic` gossip.
|
||||
|
||||
## When not to use
|
||||
|
||||
Single-process tools with no P2P topic (use local APIs only or skip `ready()`).
|
||||
Simple allowlists without attestations or history.
|
||||
|
||||
## Quick start
|
||||
|
||||
```js
|
||||
const { HyperP2PReputationSystem } = require('hyper-p2p-reputation-system')
|
||||
const topic = process.argv[2] // 64-char hex or string
|
||||
const mod = new HyperP2PReputationSystem({ topic, enableBackgroundTimers: false })
|
||||
await mod.ready() // joins swarm when topic set
|
||||
// ... application logic ...
|
||||
await mod.close()
|
||||
const HyperP2PReputationSystem = require('hyper-p2p-reputation-system')
|
||||
const rep = new HyperP2PReputationSystem({ topic: 'trust', enableBackgroundTimers: false })
|
||||
await rep.ready()
|
||||
await rep.attest('peer-abc', 10, { reason: 'good relay' })
|
||||
console.log(rep.getReputation('peer-abc'))
|
||||
await rep.close()
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [docs/api.md](docs/api.md) — constructor, methods, events, errors
|
||||
- [docs/architecture.md](docs/architecture.md) — wire types, state, composition
|
||||
- [../_shared/PRODUCTION.md](../../_shared/PRODUCTION.md) — production checklist
|
||||
- [../_shared/DOC_STANDARDS.md](../../_shared/DOC_STANDARDS.md) — documentation standards
|
||||
|
||||
- Integration: [`../../real_tests/integration/`](../../../real_tests/integration/) — `reputation-system-two-node.js`
|
||||
- [docs/api.md](docs/api.md) · [docs/architecture.md](docs/architecture.md)
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
npm install && npm test
|
||||
npm test
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Member | Description |
|
||||
|--------|-------------|
|
||||
| `new HyperP2PReputationSystem(opts?)` | `keyPair`, `topic`, `storageDir`, `decayIntervalMs`, `decayRate`, `minScore`, `maxScore`, `useHyperbee`, `enableBackgroundTimers`. |
|
||||
| `ready()` | Storage init, swarm if `topic`, decay timer if enabled. Emits `ready`. |
|
||||
| `attest(targetPeerId, delta, metadata?)` | Sign, apply locally, gossip `{ type:'attestation' }`. |
|
||||
| `receiveAttestation(attestation, targetPeerId)` | Verify + apply; false on failure. |
|
||||
| `getReputation(peerId)` | `{ score, lastUpdated, attestationsCount }`. |
|
||||
| `getTopPeers(k?)` | Sorted by score (default 10). |
|
||||
| `getHistory(peerId, limit?)` | Last N attestation records (max 100 stored). |
|
||||
| `exportSnapshot()` / `importSnapshot(snapshot)` | Portable state v1.x. |
|
||||
| `getCausalTick()` | Monotonic local counter for hooks. |
|
||||
| `simulateRemoteAttestation(...)` | Test helper (non-crypto sig). |
|
||||
| `getStats()` | `{ ops, errors }`. |
|
||||
| `close()` | Stop timers, destroy swarm/core, persist. |
|
||||
|
||||
**Events:** `ready`, `attestation`, `scoreUpdated`, `peerBanned`, `decay` via timer, `snapshotImported`, `hyperbee-fallback`, `error`, `close`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
scores Map(peerHex -> { score, lastUpdated, attestationsCount })
|
||||
history Map(peerHex -> [{ ts, delta, attester, signature, metadata }])
|
||||
_verifyAttestation: Ed25519 verify, nonce dedup, 7-day TTL
|
||||
Persistence: state.json or optional Hyperbee on storageDir
|
||||
Decay: multiplicative per interval when enableBackgroundTimers
|
||||
```
|
||||
|
||||
Gossip applies remote attestations via `_applyAttestation` on `attestation` messages.
|
||||
|
||||
## Wire table
|
||||
|
||||
| `type` | Fields | Direction |
|
||||
|--------|--------|-----------|
|
||||
| `attestation` | `target` (hex), `attestation` `{ payload, signature, nonce }` | Any → all |
|
||||
|
||||
Payload JSON (base64): `{ target, delta, attester, nonce, ts, metadata }`.
|
||||
|
||||
## Errors
|
||||
|
||||
- `importSnapshot`: invalid object or unsupported `version`.
|
||||
- `receiveAttestation`: returns `false` on verify/TTL/replay failure.
|
||||
- Replay nonces increment `metrics.sybilAttemptsBlocked`.
|
||||
|
||||
## Composition
|
||||
|
||||
- Ban hook: `peerBanned` when decayed score ≤ `minScore`.
|
||||
- Optional Hyperbee under `storageDir/reputation-core`.
|
||||
- `getCausalTick()` for event-bus / vector-clock integration.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const HyperP2PReputationSystem = require('hyper-p2p-reputation-system')
|
||||
|
||||
const rep = new HyperP2PReputationSystem({
|
||||
topic: process.argv[2],
|
||||
enableBackgroundTimers: false,
|
||||
storageDir: './rep-storage'
|
||||
})
|
||||
await rep.ready()
|
||||
|
||||
await rep.attest('peer-abc', 10, { reason: 'good relay' })
|
||||
console.log(rep.getReputation('peer-abc'))
|
||||
console.log(rep.getTopPeers(5))
|
||||
await rep.close()
|
||||
```
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
require('bare-process/global')
|
||||
const { HyperP2PReputationSystem } = require('../index.js')
|
||||
|
||||
async function main () {
|
||||
const rep = new HyperP2PReputationSystem()
|
||||
await rep.ready()
|
||||
rep.attest('peer-b', 10, { reason: 'helpful' })
|
||||
console.log('[reputation]', rep.getReputation('peer-b'), rep.getStats())
|
||||
await rep.close()
|
||||
}
|
||||
main().catch(console.error)
|
||||
@@ -1,34 +1,87 @@
|
||||
# hyper-p2p-session-rotation
|
||||
|
||||
Production module: Session token rotation.
|
||||
Rotate session tokens with monotonic `version`; gossip so peers keep highest version per `sessionId`.
|
||||
|
||||
**Protocol:** `session-rotation/v1`
|
||||
**Category:** trust-security · **Protocol:** `session-rotation/v1` · **Exports:** `HyperP2PSessionRotation`, `PROTOCOL`
|
||||
|
||||
## When to use
|
||||
|
||||
Refreshing session tokens.
|
||||
Invalidate old session tokens across a mesh after compromise or TTL.
|
||||
|
||||
## When not to use
|
||||
|
||||
Immutable sessions.
|
||||
Stateless sessions with no shared rotation ledger.
|
||||
|
||||
## Quick start
|
||||
|
||||
```js
|
||||
const { HyperP2PSessionRotation } = require('hyper-p2p-session-rotation')
|
||||
const m = new HyperP2PSessionRotation()
|
||||
m.rotateSession('s', 'tok')
|
||||
await m.ready()
|
||||
await m.close()
|
||||
const rot = new HyperP2PSessionRotation({ topic: 'sessions' })
|
||||
await rot.ready()
|
||||
rot.rotateSession('user-sess', 'token-v2')
|
||||
console.log(rot.getSession('user-sess'))
|
||||
await rot.close()
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
- [docs/api.md](docs/api.md)
|
||||
- [docs/architecture.md](docs/architecture.md)
|
||||
- [docs/api.md](docs/api.md) · [docs/architecture.md](docs/architecture.md)
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
npm install && npm test
|
||||
npm test
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Member | Description |
|
||||
|--------|-------------|
|
||||
| `new HyperP2PSessionRotation(opts?)` | `topic`, `keyPair`. |
|
||||
| `rotateSession(sessionId, newToken)` | Bump `version`, gossip `session-rotate`. |
|
||||
| `getSession(sessionId)` | `{ sessionId, token, version, rotatedAt }` or `null`. |
|
||||
| `listSessions()` | All session records. |
|
||||
| `getStats()` | `{ rotations, gossipIn, gossipOut, sessions, protocol }`. |
|
||||
| `ready()` / `close()` | Swarm lifecycle. |
|
||||
|
||||
**Events:** `rotated`, `closed`. Remote merge accepts when `d.version >= prev.version`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
_sessions Map(sessionId -> { token, version, rotatedAt })
|
||||
Each rotateSession increments version from previous or 0
|
||||
```
|
||||
|
||||
Token is opaque string; no crypto in this module.
|
||||
|
||||
## Wire table
|
||||
|
||||
| `type` | Fields | Direction |
|
||||
|--------|--------|-----------|
|
||||
| `session-rotate` | `sessionId`, `token`, `version`, `rotatedAt` | Any → all |
|
||||
|
||||
## Errors
|
||||
|
||||
- Empty `sessionId` or `newToken` on `rotateSession` / `getSession`.
|
||||
- Stale remote rotations ignored when incoming `version` is lower than local.
|
||||
|
||||
## Composition
|
||||
|
||||
- Validate `token` on inbound connections after rotation.
|
||||
- Works with `hyper-p2p-noise-session-wrap` session ids.
|
||||
- `listSessions()` for admin/debug dashboards.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
const { HyperP2PSessionRotation } = require('hyper-p2p-session-rotation')
|
||||
|
||||
const rot = new HyperP2PSessionRotation({ topic: process.argv[2] })
|
||||
await rot.ready()
|
||||
|
||||
rot.rotateSession('user-sess', 'token-v1')
|
||||
rot.rotateSession('user-sess', 'token-v2')
|
||||
console.log(rot.getSession('user-sess'))
|
||||
await rot.close()
|
||||
```
|
||||
|
||||
@@ -1,26 +1,88 @@
|
||||
# API: hyper-p2p-session-rotation
|
||||
|
||||
**Protocol:** `session-rotation/v1` · **Export:** `HyperP2PSessionRotation`
|
||||
**Protocol:** `session-rotation/v1` · **Export:** `HyperP2PSessionRotation`, `PROTOCOL`
|
||||
|
||||
## Overview
|
||||
|
||||
Rotates session tokens with monotonic `version` per `sessionId`. Gossips rotations; remote merges when `version >=` local.
|
||||
|
||||
## Constructor
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `topic` | `string` \| `Buffer` | `null` | Swarm topic |
|
||||
| `keyPair` | `KeyPair` | random | Identity |
|
||||
|
||||
## Methods
|
||||
|
||||
### `rotateSession(...)`
|
||||
### `rotateSession(sessionId, newToken)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** `{ sessionId, token, version, rotatedAt }`
|
||||
- **Throws:** `assertNonEmpty` on both args
|
||||
- **Gossip:** `{ type: 'session-rotate', sessionId, token, version, rotatedAt }`
|
||||
- **Emits:** `rotated`
|
||||
|
||||
### `getSession(...)`
|
||||
### `getSession(sessionId)`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** session or `null`
|
||||
|
||||
### `listSessions(...)`
|
||||
### `listSessions()`
|
||||
|
||||
Domain API.
|
||||
- **Returns:** all session values
|
||||
|
||||
### `ready()` / `close()`
|
||||
|
||||
### `getStats()` / `ready()` / `close()`
|
||||
Standard.
|
||||
|
||||
Lifecycle helpers.
|
||||
## Events
|
||||
|
||||
| Event | Payload |
|
||||
|-------|---------|
|
||||
| `rotated` | session record |
|
||||
| `closed` | — |
|
||||
|
||||
## getStats()
|
||||
|
||||
`rotations`, `gossipIn`, `gossipOut`, `sessions`, `protocol`.
|
||||
|
||||
## Wire
|
||||
|
||||
| type | fields | behavior |
|
||||
|------|--------|----------|
|
||||
| `session-rotate` | `sessionId`, `token`, `version`, `rotatedAt` | Replace if newer version |
|
||||
|
||||
## Errors
|
||||
|
||||
`assertNonEmpty` on session id and token.
|
||||
|
||||
See [`../../_shared/ERROR_CODES.md`](../../_shared/ERROR_CODES.md).
|
||||
|
||||
## P2P
|
||||
|
||||
Joins Hyperswarm when `topic` is set.
|
||||
Version starts at 1 on first rotation for a session.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cd modules/trust-security/hyper-p2p-session-rotation && npm test
|
||||
```
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-pear-runtime-session`, `hyper-p2p-noise-session-wrap`, `hyper-p2p-key-rotation`.
|
||||
|
||||
## Example
|
||||
|
||||
See [`examples/basic.js`](../examples/basic.js).
|
||||
|
||||
## Remote merge rules
|
||||
|
||||
- `session-rotate` applied when `!prev || d.version >= prev.version`
|
||||
|
||||
## Lifecycle
|
||||
|
||||
`version` increments on each local `rotateSession` call.
|
||||
|
||||
## See also
|
||||
|
||||
[`docs/architecture.md`](architecture.md), [`../../MODULE_CATEGORIES.md`](../../MODULE_CATEGORIES.md).
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
# Architecture: hyper-p2p-session-rotation
|
||||
|
||||
**Protocol:** `session-rotation/v1` · **Category:** trust-security
|
||||
|
||||
## Wire messages
|
||||
|
||||
| type | fields | direction | behavior |
|
||||
|------|--------|-----------|----------|
|
||||
| `session-rotate` | sessionId, token, version | gossip | Rotate |
|
||||
| type | direction | fields | behavior |
|
||||
|------|-----------|--------|----------|
|
||||
| `session-rotate` | gossip | `sessionId`, `token`, `version`, `rotatedAt` | LWW by `version` |
|
||||
|
||||
## State
|
||||
## State model
|
||||
|
||||
In-memory structures; gossip via `initModuleSwarm` / `gossipSend` when P2P enabled.
|
||||
`_sessions`: Map sessionId → `{ sessionId, token, version, rotatedAt }`.
|
||||
|
||||
## Composition
|
||||
|
||||
`hyper-pear-runtime-session`, `hyper-p2p-noise-session-wrap`.
|
||||
|
||||
Reference in New Issue
Block a user