@real-life/trust-protocol 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/dist/core.d.ts +32 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +312 -0
- package/dist/core.js.map +1 -0
- package/dist/crypto.d.ts +32 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +114 -0
- package/dist/crypto.js.map +1 -0
- package/dist/delivery.d.ts +51 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +170 -0
- package/dist/delivery.js.map +1 -0
- package/dist/encounter.d.ts +63 -0
- package/dist/encounter.d.ts.map +1 -0
- package/dist/encounter.js +56 -0
- package/dist/encounter.js.map +1 -0
- package/dist/identity.d.ts +25 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +87 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/probe/continuity.d.ts +99 -0
- package/dist/probe/continuity.d.ts.map +1 -0
- package/dist/probe/continuity.js +303 -0
- package/dist/probe/continuity.js.map +1 -0
- package/dist/probe/deps.d.ts +61 -0
- package/dist/probe/deps.d.ts.map +1 -0
- package/dist/probe/deps.js +54 -0
- package/dist/probe/deps.js.map +1 -0
- package/dist/probe/introduce.d.ts +70 -0
- package/dist/probe/introduce.d.ts.map +1 -0
- package/dist/probe/introduce.js +295 -0
- package/dist/probe/introduce.js.map +1 -0
- package/dist/probe/membership.d.ts +90 -0
- package/dist/probe/membership.d.ts.map +1 -0
- package/dist/probe/membership.js +450 -0
- package/dist/probe/membership.js.map +1 -0
- package/dist/probe/trust.d.ts +116 -0
- package/dist/probe/trust.d.ts.map +1 -0
- package/dist/probe/trust.js +345 -0
- package/dist/probe/trust.js.map +1 -0
- package/dist/probe.d.ts +8 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +41 -0
- package/dist/probe.js.map +1 -0
- package/dist/schemas.d.ts +2 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +51 -0
- package/dist/schemas.js.map +1 -0
- package/dist/unicode15.d.ts +8 -0
- package/dist/unicode15.d.ts.map +1 -0
- package/dist/unicode15.js +55 -0
- package/dist/unicode15.js.map +1 -0
- package/package.json +55 -0
package/dist/identity.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// identity — the Identity Layer: one root seed, every context derived.
|
|
2
|
+
//
|
|
3
|
+
// A person holds ONE secret. Everything else is derived from it through
|
|
4
|
+
// the ordinary rltp/anchor family: the community anchor that carries the
|
|
5
|
+
// stable social identity, a pair anchor per relationship, a member
|
|
6
|
+
// anchor per group, public personas. No two contexts link to each other
|
|
7
|
+
// unless their holder deliberately discloses the link.
|
|
8
|
+
//
|
|
9
|
+
// The label registry is CLOSED (Identity §6.1, unchanged since 0.13):
|
|
10
|
+
// exactly three label forms derive, and this API rejects every other
|
|
11
|
+
// string before any key derivation — fail closed, no repair. `self`,
|
|
12
|
+
// `recovery`, `carrier` and `device/…` are not labels; recovery and the
|
|
13
|
+
// carrier-relationship identity have their own fixed derivations outside
|
|
14
|
+
// this registry and are deliberately NOT part of this module yet.
|
|
15
|
+
//
|
|
16
|
+
// Fresh-always (Encounter §4.4): a ceremony mints a pair context whose
|
|
17
|
+
// label digests the NONCE BYTES, so nothing correlatable is displayed —
|
|
18
|
+
// not even to someone who scanned the same person yesterday.
|
|
19
|
+
import { toU } from './core.js';
|
|
20
|
+
import { hkdf, edFromSeed, xFromSeed, anchorOfEd, mkOfX, digestBytes } from './crypto.js';
|
|
21
|
+
import { allowed15, WHITE_SPACE_15 } from './unicode15.js';
|
|
22
|
+
// ── the closed registry (Identity §6.1) ─────────────────────────────────
|
|
23
|
+
// A digest component is the ONE canonical u rendering of a sha2-256
|
|
24
|
+
// multihash: 47 characters, no padding, no non-zero trailing bits, no z.
|
|
25
|
+
const digestComponent = (d) => d.length === 47 && d[0] === 'u' && toU(d) === d;
|
|
26
|
+
// §6.2 — the ordered persona-name pipeline. NFC is the one permitted
|
|
27
|
+
// normalization (applied, not rejected); every later check runs on the
|
|
28
|
+
// NFC result — and every Unicode property is evaluated against the
|
|
29
|
+
// PINNED 15.0 data in unicode15.ts, never the platform's tables (review
|
|
30
|
+
// 2, B-1: Node 22 ships Unicode 16 — a post-15.0 code point must not
|
|
31
|
+
// derive an anchor on one runtime and be rejected on another).
|
|
32
|
+
const personaName = (raw) => {
|
|
33
|
+
if (!raw.isWellFormed())
|
|
34
|
+
return null; // no lone surrogates
|
|
35
|
+
const name = raw.normalize('NFC'); // NFC of 15.0-assigned code points is version-stable by Unicode's guarantee
|
|
36
|
+
const cps = [...name];
|
|
37
|
+
if (cps.length < 1 || cps.length > 64)
|
|
38
|
+
return null;
|
|
39
|
+
if (new TextEncoder().encode(name).length > 256)
|
|
40
|
+
return null;
|
|
41
|
+
if (name.includes('/'))
|
|
42
|
+
return null; // labels have exactly the components the registry shows
|
|
43
|
+
for (const c of cps)
|
|
44
|
+
if (!allowed15(c.codePointAt(0)))
|
|
45
|
+
return null; // assigned in 15.0, no Cc/Cf
|
|
46
|
+
if (WHITE_SPACE_15.has(cps[0].codePointAt(0)) || WHITE_SPACE_15.has(cps[cps.length - 1].codePointAt(0)))
|
|
47
|
+
return null;
|
|
48
|
+
return name;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The canonical form of a registry label, or null for everything the
|
|
52
|
+
* closed registry rejects. Personas come back NFC-normalized — for them
|
|
53
|
+
* the canonical label may differ from the input; label equality IS
|
|
54
|
+
* anchor identity, so derivation always uses the canonical form.
|
|
55
|
+
*/
|
|
56
|
+
export function canonicalLabel(label) {
|
|
57
|
+
if (typeof label !== 'string')
|
|
58
|
+
return null;
|
|
59
|
+
if (label.startsWith('group/'))
|
|
60
|
+
return digestComponent(label.slice(6)) ? label : null;
|
|
61
|
+
if (label.startsWith('pair/'))
|
|
62
|
+
return digestComponent(label.slice(5)) ? label : null;
|
|
63
|
+
if (label.startsWith('persona/')) {
|
|
64
|
+
const name = personaName(label.slice(8));
|
|
65
|
+
return name === null ? null : 'persona/' + name;
|
|
66
|
+
}
|
|
67
|
+
return null; // self, recovery, carrier, device/…, and every unknown form: fail closed
|
|
68
|
+
}
|
|
69
|
+
export async function labeledContext(rootIkm, label) {
|
|
70
|
+
const canonical = canonicalLabel(label);
|
|
71
|
+
if (canonical === null)
|
|
72
|
+
throw new Error(`not a label of the closed registry (Identity §6.1): ${JSON.stringify(label)}`);
|
|
73
|
+
const ed = await edFromSeed(await hkdf(rootIkm, 'rltp/anchor/ed/' + canonical));
|
|
74
|
+
const x = await xFromSeed(await hkdf(rootIkm, 'rltp/anchor/x/' + canonical));
|
|
75
|
+
return { label: canonical, ed, x, anchor: anchorOfEd(ed.pubRaw), keyAgreement: mkOfX(x.pubRaw) };
|
|
76
|
+
}
|
|
77
|
+
// fresh-always (Encounter §4.4): label = pair/<multihash over the NONCE
|
|
78
|
+
// BYTES>. The nonce is the holder's own, fresh, and exactly 32 bytes —
|
|
79
|
+
// a shorter one weakens the label to below its specified entropy (B-2).
|
|
80
|
+
export async function pairContext(rootIkm, nonce) {
|
|
81
|
+
if (!(nonce instanceof Uint8Array) || nonce.length !== 32)
|
|
82
|
+
throw new Error('pair nonce must be exactly 32 bytes (Identity §6.1)');
|
|
83
|
+
return labeledContext(rootIkm, 'pair/' + await digestBytes(nonce));
|
|
84
|
+
}
|
|
85
|
+
/** The community anchor: an ordinary group context over the community's genesis digest. */
|
|
86
|
+
export const communityContext = (rootIkm, genesisDigest) => labeledContext(rootIkm, 'group/' + genesisDigest);
|
|
87
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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","sourcesContent":["// identity — the Identity Layer: one root seed, every context derived.\n//\n// A person holds ONE secret. Everything else is derived from it through\n// the ordinary rltp/anchor family: the community anchor that carries the\n// stable social identity, a pair anchor per relationship, a member\n// anchor per group, public personas. No two contexts link to each other\n// unless their holder deliberately discloses the link.\n//\n// The label registry is CLOSED (Identity §6.1, unchanged since 0.13):\n// exactly three label forms derive, and this API rejects every other\n// string before any key derivation — fail closed, no repair. `self`,\n// `recovery`, `carrier` and `device/…` are not labels; recovery and the\n// carrier-relationship identity have their own fixed derivations outside\n// this registry and are deliberately NOT part of this module yet.\n//\n// Fresh-always (Encounter §4.4): a ceremony mints a pair context whose\n// label digests the NONCE BYTES, so nothing correlatable is displayed —\n// not even to someone who scanned the same person yesterday.\nimport { toU } from './core.js'\nimport { hkdf, edFromSeed, xFromSeed, anchorOfEd, mkOfX, digestBytes } from './crypto.js'\nimport type { KeyPair } from './crypto.js'\nimport { allowed15, WHITE_SPACE_15 } from './unicode15.js'\n\n/**\n * One derived identity context: its label, both key pairs, and the two\n * public forms others see — the did:key anchor and the multikey for key\n * agreement. Contexts never link to each other without a deliberate act.\n */\nexport interface Context {\n label: string\n ed: KeyPair\n x: KeyPair\n anchor: string\n keyAgreement: string\n}\n\n// ── the closed registry (Identity §6.1) ─────────────────────────────────\n// A digest component is the ONE canonical u rendering of a sha2-256\n// multihash: 47 characters, no padding, no non-zero trailing bits, no z.\nconst digestComponent = (d: string): boolean => d.length === 47 && d[0] === 'u' && toU(d) === d\n\n// §6.2 — the ordered persona-name pipeline. NFC is the one permitted\n// normalization (applied, not rejected); every later check runs on the\n// NFC result — and every Unicode property is evaluated against the\n// PINNED 15.0 data in unicode15.ts, never the platform's tables (review\n// 2, B-1: Node 22 ships Unicode 16 — a post-15.0 code point must not\n// derive an anchor on one runtime and be rejected on another).\nconst personaName = (raw: string): string | null => {\n if (!(raw as unknown as { isWellFormed(): boolean }).isWellFormed()) return null // no lone surrogates\n const name = raw.normalize('NFC') // NFC of 15.0-assigned code points is version-stable by Unicode's guarantee\n const cps = [...name]\n if (cps.length < 1 || cps.length > 64) return null\n if (new TextEncoder().encode(name).length > 256) return null\n if (name.includes('/')) return null // labels have exactly the components the registry shows\n for (const c of cps) if (!allowed15(c.codePointAt(0)!)) return null // assigned in 15.0, no Cc/Cf\n if (WHITE_SPACE_15.has(cps[0]!.codePointAt(0)!) || WHITE_SPACE_15.has(cps[cps.length - 1]!.codePointAt(0)!)) return null\n return name\n}\n\n/**\n * The canonical form of a registry label, or null for everything the\n * closed registry rejects. Personas come back NFC-normalized — for them\n * the canonical label may differ from the input; label equality IS\n * anchor identity, so derivation always uses the canonical form.\n */\nexport function canonicalLabel (label: string): string | null {\n if (typeof label !== 'string') return null\n if (label.startsWith('group/')) return digestComponent(label.slice(6)) ? label : null\n if (label.startsWith('pair/')) return digestComponent(label.slice(5)) ? label : null\n if (label.startsWith('persona/')) {\n const name = personaName(label.slice(8))\n return name === null ? null : 'persona/' + name\n }\n return null // self, recovery, carrier, device/…, and every unknown form: fail closed\n}\n\nexport async function labeledContext (rootIkm: Uint8Array, label: string): Promise<Context> {\n const canonical = canonicalLabel(label)\n if (canonical === null) throw new Error(`not a label of the closed registry (Identity §6.1): ${JSON.stringify(label)}`)\n const ed = await edFromSeed(await hkdf(rootIkm, 'rltp/anchor/ed/' + canonical))\n const x = await xFromSeed(await hkdf(rootIkm, 'rltp/anchor/x/' + canonical))\n return { label: canonical, ed, x, anchor: anchorOfEd(ed.pubRaw), keyAgreement: mkOfX(x.pubRaw) }\n}\n\n// fresh-always (Encounter §4.4): label = pair/<multihash over the NONCE\n// BYTES>. The nonce is the holder's own, fresh, and exactly 32 bytes —\n// a shorter one weakens the label to below its specified entropy (B-2).\nexport async function pairContext (rootIkm: Uint8Array, nonce: Uint8Array): Promise<Context> {\n if (!(nonce instanceof Uint8Array) || nonce.length !== 32) throw new Error('pair nonce must be exactly 32 bytes (Identity §6.1)')\n return labeledContext(rootIkm, 'pair/' + await digestBytes(nonce))\n}\n\n/** The community anchor: an ordinary group context over the community's genesis digest. */\nexport const communityContext = (rootIkm: Uint8Array, genesisDigest: string): Promise<Context> =>\n labeledContext(rootIkm, 'group/' + genesisDigest)\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwBA,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @real-life/trust-protocol — the cryptographic core of RLTP.
|
|
2
|
+
//
|
|
3
|
+
// What this entry point IS: the wire-level primitives of the converged
|
|
4
|
+
// layers, reproducing the shipped test vectors byte for byte — the
|
|
5
|
+
// canonical form, the derivation of identity contexts under the closed
|
|
6
|
+
// label registry, the encounter wire builders, the sealed envelope and
|
|
7
|
+
// the generic receive chain.
|
|
8
|
+
//
|
|
9
|
+
// What it is NOT (yet): the layers' state machines. Identity's recovery
|
|
10
|
+
// and carrier derivations, Encounter's ceremony record and acceptance
|
|
11
|
+
// machinery, Delivery's type-specific stages and residual bookkeeping
|
|
12
|
+
// live in the specification and its conformance fixtures, not here. A
|
|
13
|
+
// conformant implementation builds ON this module; importing it is not
|
|
14
|
+
// yet conformance. The README states per module which casting's wire
|
|
15
|
+
// forms it implements.
|
|
16
|
+
//
|
|
17
|
+
// core the form every artifact obeys
|
|
18
|
+
// crypto the primitives, and the Data Integrity proof
|
|
19
|
+
// identity context derivation under the closed registry (§6.1/§6.2)
|
|
20
|
+
// encounter the ceremony's wire forms: challenges, cards, credentials
|
|
21
|
+
// delivery the sealed envelope and the generic receive stages 1–4
|
|
22
|
+
//
|
|
23
|
+
// Specification, schemas and vectors: https://rltp.real-life.org
|
|
24
|
+
export * from './core.js';
|
|
25
|
+
export * from './crypto.js';
|
|
26
|
+
export * from './identity.js';
|
|
27
|
+
export * from './encounter.js';
|
|
28
|
+
export * from './delivery.js';
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,uEAAuE;AACvE,mEAAmE;AACnE,uEAAuE;AACvE,uEAAuE;AACvE,6BAA6B;AAC7B,EAAE;AACF,wEAAwE;AACxE,sEAAsE;AACtE,sEAAsE;AACtE,sEAAsE;AACtE,uEAAuE;AACvE,qEAAqE;AACrE,uBAAuB;AACvB,EAAE;AACF,6CAA6C;AAC7C,4DAA4D;AAC5D,wEAAwE;AACxE,yEAAyE;AACzE,sEAAsE;AACtE,EAAE;AACF,iEAAiE;AAEjE,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA","sourcesContent":["// @real-life/trust-protocol — the cryptographic core of RLTP.\n//\n// What this entry point IS: the wire-level primitives of the converged\n// layers, reproducing the shipped test vectors byte for byte — the\n// canonical form, the derivation of identity contexts under the closed\n// label registry, the encounter wire builders, the sealed envelope and\n// the generic receive chain.\n//\n// What it is NOT (yet): the layers' state machines. Identity's recovery\n// and carrier derivations, Encounter's ceremony record and acceptance\n// machinery, Delivery's type-specific stages and residual bookkeeping\n// live in the specification and its conformance fixtures, not here. A\n// conformant implementation builds ON this module; importing it is not\n// yet conformance. The README states per module which casting's wire\n// forms it implements.\n//\n// core the form every artifact obeys\n// crypto the primitives, and the Data Integrity proof\n// identity context derivation under the closed registry (§6.1/§6.2)\n// encounter the ceremony's wire forms: challenges, cards, credentials\n// delivery the sealed envelope and the generic receive stages 1–4\n//\n// Specification, schemas and vectors: https://rltp.real-life.org\n\nexport * from './core.js'\nexport * from './crypto.js'\nexport * from './identity.js'\nexport * from './encounter.js'\nexport * from './delivery.js'\n"]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as C from './deps.js';
|
|
2
|
+
import type { Person } from './deps.js';
|
|
3
|
+
export declare const snapshotPriors: (p: Person) => {
|
|
4
|
+
own: any[];
|
|
5
|
+
counterpart: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function buildProbe(p: Person, newKey: string, ent?: any): Promise<{
|
|
8
|
+
to: any;
|
|
9
|
+
kind: string;
|
|
10
|
+
env: C.Envelope;
|
|
11
|
+
}[] | null>;
|
|
12
|
+
export declare function receiveContinuity(p: Person, env: any, when: number, ent?: any): Promise<{
|
|
13
|
+
handled: boolean;
|
|
14
|
+
partial: boolean;
|
|
15
|
+
error?: undefined;
|
|
16
|
+
matches?: undefined;
|
|
17
|
+
chained?: undefined;
|
|
18
|
+
name?: undefined;
|
|
19
|
+
outbound?: undefined;
|
|
20
|
+
outboundError?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
handled: boolean;
|
|
23
|
+
matches: number;
|
|
24
|
+
error?: undefined;
|
|
25
|
+
partial?: undefined;
|
|
26
|
+
chained?: undefined;
|
|
27
|
+
name?: undefined;
|
|
28
|
+
outbound?: undefined;
|
|
29
|
+
outboundError?: undefined;
|
|
30
|
+
} | {
|
|
31
|
+
handled: boolean;
|
|
32
|
+
matches: number;
|
|
33
|
+
chained: boolean;
|
|
34
|
+
name: any;
|
|
35
|
+
outbound: {
|
|
36
|
+
to: any;
|
|
37
|
+
kind: string;
|
|
38
|
+
env: C.Envelope;
|
|
39
|
+
}[];
|
|
40
|
+
outboundError: string | undefined;
|
|
41
|
+
error?: undefined;
|
|
42
|
+
partial?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
handled: boolean;
|
|
45
|
+
error: string;
|
|
46
|
+
chained?: undefined;
|
|
47
|
+
name?: undefined;
|
|
48
|
+
outbound?: undefined;
|
|
49
|
+
outboundError?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
handled: boolean;
|
|
52
|
+
chained: boolean;
|
|
53
|
+
name: any;
|
|
54
|
+
outbound: {
|
|
55
|
+
to: any;
|
|
56
|
+
kind: string;
|
|
57
|
+
env: C.Envelope;
|
|
58
|
+
}[];
|
|
59
|
+
outboundError: string;
|
|
60
|
+
error?: undefined;
|
|
61
|
+
} | {
|
|
62
|
+
handled: boolean;
|
|
63
|
+
chained: boolean;
|
|
64
|
+
name: any;
|
|
65
|
+
outbound: {
|
|
66
|
+
to: any;
|
|
67
|
+
kind: string;
|
|
68
|
+
env: C.Envelope;
|
|
69
|
+
}[];
|
|
70
|
+
error?: undefined;
|
|
71
|
+
outboundError?: undefined;
|
|
72
|
+
} | {
|
|
73
|
+
handled: boolean;
|
|
74
|
+
doc: any;
|
|
75
|
+
} | {
|
|
76
|
+
handled: boolean;
|
|
77
|
+
duplicate: boolean;
|
|
78
|
+
} | {
|
|
79
|
+
handled: boolean;
|
|
80
|
+
duplicate?: undefined;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* The host's producing call for a mapping the receiver still owes
|
|
84
|
+
* (review 6, B-3): a transient seal failure during probe/mapping
|
|
85
|
+
* handling persists the obligation in contMappingDue; this call — not a
|
|
86
|
+
* redelivery, which the freshness rule forbids — builds and returns it.
|
|
87
|
+
*/
|
|
88
|
+
export declare function flushContinuity(p: Person, newKey: string, when: number, ent?: any): Promise<{
|
|
89
|
+
outbound: never[];
|
|
90
|
+
pending: boolean;
|
|
91
|
+
} | {
|
|
92
|
+
outbound: {
|
|
93
|
+
to: any;
|
|
94
|
+
kind: string;
|
|
95
|
+
env: C.Envelope;
|
|
96
|
+
}[];
|
|
97
|
+
pending?: undefined;
|
|
98
|
+
}>;
|
|
99
|
+
//# sourceMappingURL=continuity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuity.d.ts","sourceRoot":"","sources":["../../src/probe/continuity.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,CAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAiBvC,eAAO,MAAM,cAAc,GAAI,GAAG,MAAM;;;CAGtC,CAAA;AAGF,wBAAsB,UAAU,CAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAE,GAAQ;;;;YAkBzE;AAgDD,wBAAsB,iBAAiB,CAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOxF;AAsGD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAQ;;;;;;;;;;GAkB5F"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// continuity.mjs — rltp-visibility §6a im Simulator: continuity-probe@1 +
|
|
2
|
+
// continuity-mapping@1. „Kontinuität wird NACH der Zeremonie aufgelöst,
|
|
3
|
+
// nie in ihr" (Encounter 4.4): jedes Enactment UND jede Vorstellung prägt
|
|
4
|
+
// ein frisches Tupel; ob es ein Wiedersehen war, klärt die Probe über den
|
|
5
|
+
// frischen Kanal — automatisch, ohne Dialog, ohne Namen.
|
|
6
|
+
//
|
|
7
|
+
// Die Probe ist das dritte Mitglied EINER Familie (Stern, Mapping, Probe:
|
|
8
|
+
// geblendete Werte unter Beziehungs-Schlüsseln): die EIGENEN bisherigen
|
|
9
|
+
// pair-Anker reisen als HMAC unter k_p (DH des NEUEN Kanals), gepolstert
|
|
10
|
+
// auf Vielfache von 256, sortiert, sequenziert. Nur wer eine der
|
|
11
|
+
// Beziehungen wirklich hält, kann matchen; ein leerer Bestand sendet
|
|
12
|
+
// trotzdem (sonst leakte die Abwesenheit die Kardinalität „null").
|
|
13
|
+
//
|
|
14
|
+
// Die Maschine (6a.4): EIN Wähler (Record-Seite = lexikographisch
|
|
15
|
+
// kleinerer neuer pair-Anker), EINE endgültige Wahl (eingefroren,
|
|
16
|
+
// Abweichung = Äquivokation), EIN Auslöser (Record-Seite kettet atomar
|
|
17
|
+
// mit ihrer Wahl; die Gegenseite NUR auf deren verifiziertes Mapping —
|
|
18
|
+
// ihr eigenes ist ein Match-Report). Kette: neues Tupel wird Kopf, das
|
|
19
|
+
// alte deaktiviert; Evidenz akkumuliert AUF DER BEZIEHUNG (✓ bleibt ✓),
|
|
20
|
+
// für Dritte unsichtbar. Wer die Probe nicht beantworten kann, IST
|
|
21
|
+
// protokollisch eine neue Beziehung (Identity 9.3).
|
|
22
|
+
import { jcs } from '../core.js';
|
|
23
|
+
import * as C from './deps.js';
|
|
24
|
+
import { hmac } from './trust.js';
|
|
25
|
+
const say = (p, m) => p.log.push(m);
|
|
26
|
+
const CHUNK = 256;
|
|
27
|
+
// ── Schlüssel (Spec-Infostrings, richtungsgebunden) ─────────────────────
|
|
28
|
+
// k_p = HKDF(ECDH(newPairX_sender, newPairX_recipient),
|
|
29
|
+
// 'rltp/visibility/blind/probe/' + senderNewPair + '/' + recipientNewPair)
|
|
30
|
+
const kProbe = async (xPriv, theirKa, senderAnchor, recipientAnchor) => C.hkdf(await C.ecdh(xPriv, C.xRawOfMk(theirKa)), 'rltp/visibility/blind/probe/' + senderAnchor + '/' + recipientAnchor);
|
|
31
|
+
const kMac = async (xPriv, theirKa, info) => C.hkdf(await C.ecdh(xPriv, C.xRawOfMk(theirKa)), info);
|
|
32
|
+
// ── Schnappschuss-Helfer (Prior-Candidate-Set, Sicht des Halters) ───────
|
|
33
|
+
// wird von introduce.mjs BEI TUPEL-ERZEUGUNG aufgerufen: aktive Heads,
|
|
34
|
+
// das frische Tupel selbst ausgeschlossen; deaktivierte Glieder nie.
|
|
35
|
+
export const snapshotPriors = (p) => ({
|
|
36
|
+
own: [...p.contacts.values()].filter((c) => !c.deactivated && c.channel?.own).map((c) => c.channel.own.anchor),
|
|
37
|
+
counterpart: [...p.contacts.entries()].filter(([, c]) => !c.deactivated).map(([k]) => k),
|
|
38
|
+
});
|
|
39
|
+
// ── continuity-probe@1: bauen ───────────────────────────────────────────
|
|
40
|
+
export async function buildProbe(p, newKey, ent = {}) {
|
|
41
|
+
const t = p.contacts.get(newKey);
|
|
42
|
+
if (!t?.channel?.own)
|
|
43
|
+
return null;
|
|
44
|
+
t.probeSeqOut = (t.probeSeqOut ?? 0) + 1;
|
|
45
|
+
const kp = await kProbe(t.channel.own.x.priv, t.channel.counterpartKa, t.channel.own.anchor, newKey);
|
|
46
|
+
const entries = [];
|
|
47
|
+
for (const a of t.priorCands?.own ?? [])
|
|
48
|
+
entries.push(await hmac(kp, a));
|
|
49
|
+
// Polstern auf das nächste Vielfache von 256 (leerer Bestand → 1 Voll-Polster-Chunk)
|
|
50
|
+
const total = Math.max(CHUNK, Math.ceil(entries.length / CHUNK) * CHUNK);
|
|
51
|
+
while (entries.length < total)
|
|
52
|
+
entries.push(C.b64uOf(ent.pad ?? C.rand(32)));
|
|
53
|
+
entries.sort();
|
|
54
|
+
const outbound = [];
|
|
55
|
+
for (let i = 0; i * CHUNK < entries.length; i++) {
|
|
56
|
+
const body = { type: 'continuity-probe@1', probe: String(t.probeSeqOut), seq: String(i + 1), last: (i + 1) * CHUNK >= entries.length, blinded: entries.slice(i * CHUNK, (i + 1) * CHUNK) };
|
|
57
|
+
outbound.push({ to: t, kind: 'continuity-probe@1 (geblendet, gepolstert, versiegelt)', env: await C.seal({ ...body, mac: await hmac(kp, jcs(body)) }, t.channel.counterpartKa, ent) });
|
|
58
|
+
}
|
|
59
|
+
say(p, `Kontinuitäts-Probe an ${t.name}: ${t.priorCands?.own.length ?? 0} eigene Beziehungs-Anker, geblendet + gepolstert (${entries.length})`);
|
|
60
|
+
return outbound;
|
|
61
|
+
}
|
|
62
|
+
// ── continuity-mapping@1: bauen ─────────────────────────────────────────
|
|
63
|
+
// prior = EIGENER alter pair-Anker · next = eigener neuer · to = neuer der
|
|
64
|
+
// Gegenseite; mac1 unter dem ALTEN Beziehungs-Schlüssel, mac2 unter dem
|
|
65
|
+
// NEUEN — nur derselbe Schlüssel-Inhaber kann beide (für die Adressatin
|
|
66
|
+
// fälschbar: Klasse V, abstreitbar).
|
|
67
|
+
async function buildMapping(p, newKey, oldKey, when, ent = {}) {
|
|
68
|
+
const t = p.contacts.get(newKey), old = p.contacts.get(oldKey);
|
|
69
|
+
const body = { type: 'continuity-mapping@1', prior: old.channel.own.anchor, next: t.channel.own.anchor, to: newKey, revision: '1', issuedAt: C.iso(when) };
|
|
70
|
+
const msg = jcs(body);
|
|
71
|
+
const doc = { ...body,
|
|
72
|
+
mac1: await hmac(await kMac(old.channel.own.x.priv, old.channel.counterpartKa, 'rltp/visibility/mac/cont1'), msg),
|
|
73
|
+
mac2: await hmac(await kMac(t.channel.own.x.priv, t.channel.counterpartKa, 'rltp/visibility/mac/cont2'), msg) };
|
|
74
|
+
return { to: t, kind: 'continuity-mapping@1 (mac1 alt · mac2 neu, versiegelt)', env: await C.seal(doc, t.channel.counterpartKa, ent) };
|
|
75
|
+
}
|
|
76
|
+
// ── die Kette: neues Tupel wird Kopf, das alte deaktiviert ──────────────
|
|
77
|
+
// Tupel-Ebene bleibt (Stern-Salts, Probe-Sequenzen); KETTEN-Ebene wandert:
|
|
78
|
+
// Kontakt-Gedächtnis, Evidenz (✓ akkumuliert), Offenlegungen, Gruppen.
|
|
79
|
+
function chainTuple(p, newKey, oldKey) {
|
|
80
|
+
const head = p.contacts.get(newKey), old = p.contacts.get(oldKey);
|
|
81
|
+
if (!head || !old || old.deactivated)
|
|
82
|
+
return false; // idempotent
|
|
83
|
+
old.deactivated = true;
|
|
84
|
+
old.chainedInto = newKey;
|
|
85
|
+
head.chain = [...(old.chain ?? []), oldKey];
|
|
86
|
+
if (old.state === '✓' || head.state === '✓') {
|
|
87
|
+
head.state = '✓';
|
|
88
|
+
head.provenance = 'ceremony';
|
|
89
|
+
}
|
|
90
|
+
else if (old.state === '⇄' || head.state === '⇄')
|
|
91
|
+
head.state = '⇄';
|
|
92
|
+
head.since = [old.since, head.since].filter(Boolean).sort()[0];
|
|
93
|
+
head.name = old.name; // lokales Kontakt-Gedächtnis
|
|
94
|
+
head.selfAnchor ??= old.selfAnchor;
|
|
95
|
+
head.mapping ??= old.mapping;
|
|
96
|
+
head.trustGiven ??= old.trustGiven;
|
|
97
|
+
head.trustReceived ??= old.trustReceived;
|
|
98
|
+
head.trustPaused ??= old.trustPaused;
|
|
99
|
+
head.sentMapping ??= old.sentMapping;
|
|
100
|
+
head.starReceived ??= old.starReceived;
|
|
101
|
+
head.starInfo ??= old.starInfo;
|
|
102
|
+
head.sharedGroups = [...new Set([...(old.sharedGroups ?? []), ...(head.sharedGroups ?? [])])];
|
|
103
|
+
say(p, `Kontinuität: ${head.name} wiedererkannt — neues Tupel an die Beziehung gekettet (${head.state}, ${head.chain.length + 1} Enactments)`);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
// ── Empfangs-Dispatch ───────────────────────────────────────────────────
|
|
107
|
+
const iAmRecord = (t, newKey) => t.channel.own.anchor < newKey; // lexikographisch kleinerer NEUER Anker
|
|
108
|
+
const tupleOf = (p, rkid) => {
|
|
109
|
+
const ctx = p.contexts.get(rkid);
|
|
110
|
+
if (!ctx)
|
|
111
|
+
return null;
|
|
112
|
+
const e = [...p.contacts.entries()].find(([, c]) => c.channel?.own?.anchor === ctx.anchor);
|
|
113
|
+
return e ? { key: e[0], t: e[1], ctx } : null;
|
|
114
|
+
};
|
|
115
|
+
export async function receiveContinuity(p, env, when, ent = {}) {
|
|
116
|
+
const opened = await C.openEnvelope(p, env); // Stufen 1–4, Cache-Lesung
|
|
117
|
+
if (opened.duplicate)
|
|
118
|
+
return { handled: true, duplicate: true };
|
|
119
|
+
if (opened.error)
|
|
120
|
+
return { handled: false };
|
|
121
|
+
const r = await receiveContinuityInner(p, env, opened.doc, when, ent);
|
|
122
|
+
if (r?.handled && !r.error)
|
|
123
|
+
C.effectDone(p, opened.digest);
|
|
124
|
+
return r;
|
|
125
|
+
}
|
|
126
|
+
async function receiveContinuityInner(p, env, doc, when, ent = {}) {
|
|
127
|
+
const hit = tupleOf(p, env.rkid);
|
|
128
|
+
if (!hit)
|
|
129
|
+
return { handled: false };
|
|
130
|
+
if (doc?.type === 'continuity-probe@1')
|
|
131
|
+
return handleProbe(p, hit, doc, when, ent);
|
|
132
|
+
if (doc?.type === 'continuity-mapping@1')
|
|
133
|
+
return handleMapping(p, hit, doc, when, ent);
|
|
134
|
+
return { handled: false, doc };
|
|
135
|
+
}
|
|
136
|
+
async function handleProbe(p, { key, t }, doc, when, ent) {
|
|
137
|
+
// form BEFORE fields (M-3): mac, sequence and chunk list as used below
|
|
138
|
+
if (!C.shaped(doc, { mac: 'string', probe: 'string', seq: 'string', blinded: 'array' }) || typeof doc.last !== 'boolean'
|
|
139
|
+
|| !C.intStr(doc.probe) || !C.intStr(doc.seq) // NaN in probeSeqIn wäre Replay-Zustandskorruption (Review 9, P-B1)
|
|
140
|
+
|| doc.blinded.length !== 256 // die Chunkform ist FEST: genau 256 Einträge (Review 10, P-B1-Rest)
|
|
141
|
+
|| !doc.blinded.every((b) => typeof b === 'string'))
|
|
142
|
+
return { handled: true, error: 'malformed probe' };
|
|
143
|
+
// Senderseitiger k_p, aus Empfängersicht nachgerechnet (Richtung!)
|
|
144
|
+
const kp = await kProbe(t.channel.own.x.priv, t.channel.counterpartKa, key, t.channel.own.anchor);
|
|
145
|
+
const { mac, ...body } = doc;
|
|
146
|
+
if ((await hmac(kp, jcs(body))) !== mac)
|
|
147
|
+
return { handled: true, error: 'probe mac' };
|
|
148
|
+
if (BigInt(doc.probe) <= BigInt(t.probeSeqIn ?? '0'))
|
|
149
|
+
return { handled: true, error: 'probe replay (nicht streng größer)' };
|
|
150
|
+
// Chunk-Sammlung pro (Tupel, probe); höhere probe verwirft die offene
|
|
151
|
+
if (t.probeAsm?.probe !== doc.probe)
|
|
152
|
+
t.probeAsm = { probe: doc.probe, blinded: [], next: 1 };
|
|
153
|
+
if (Number(doc.seq) !== t.probeAsm.next)
|
|
154
|
+
return { handled: true, error: 'malformed probe (chunk order)' };
|
|
155
|
+
t.probeAsm.next += 1;
|
|
156
|
+
t.probeAsm.blinded.push(...doc.blinded);
|
|
157
|
+
if (!doc.last)
|
|
158
|
+
return { handled: true, partial: true };
|
|
159
|
+
// finaler Chunk: union lesen, dann vollzieht sich der EFFEKT (consume
|
|
160
|
+
// + Wahl + Kette) VOR dem fehlbaren Mapping-Bau — bewusst, denn die
|
|
161
|
+
// Frische-Regel verbietet Chunk-Replays als Retry-Weg; ein Bau-Fehler
|
|
162
|
+
// wird als getypte Pflicht persistiert und über flushContinuity()
|
|
163
|
+
// eingelöst (Reviews 5–8)
|
|
164
|
+
const union = t.probeAsm.blinded;
|
|
165
|
+
const consume = () => { t.probeAsm = null; t.probeSeqIn = doc.probe; }; // als STRING — BigInt-Vergleich, 18 Stellen exakt
|
|
166
|
+
// Schnitt gegen die GEGENSEITEN-Anker des eigenen Prior-Candidate-Sets
|
|
167
|
+
const matches = [];
|
|
168
|
+
for (const cp of t.priorCands?.counterpart ?? []) {
|
|
169
|
+
if (union.includes(await hmac(kp, cp)))
|
|
170
|
+
matches.push(cp);
|
|
171
|
+
}
|
|
172
|
+
if (!matches.length) {
|
|
173
|
+
consume();
|
|
174
|
+
say(p, `Probe von ${t.name}: kein Match — ehrlich eine neue Beziehung`);
|
|
175
|
+
return { handled: true, matches: 0 };
|
|
176
|
+
}
|
|
177
|
+
const outbound = [];
|
|
178
|
+
let chained = false;
|
|
179
|
+
let outboundError;
|
|
180
|
+
if (iAmRecord(t, key)) {
|
|
181
|
+
// Record-Seite: einmal wählen, endgültig; Kette atomar mit der Wahl
|
|
182
|
+
if (!t.contChosen) {
|
|
183
|
+
const choice = matches[0]; // Wahl frei — hier: erster Match (reichste Historie wäre App-Sache)
|
|
184
|
+
consume();
|
|
185
|
+
t.contChosen = choice;
|
|
186
|
+
chained = chainTuple(p, key, choice);
|
|
187
|
+
try {
|
|
188
|
+
outbound.push(await buildMapping(p, key, choice, when, ent));
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
t.contMappingDue = { prior: choice, kind: 'choice' };
|
|
192
|
+
outboundError = String(err?.message ?? err);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else
|
|
196
|
+
consume();
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Nicht-Record: Match-Report (löst NIE eine Kette aus)
|
|
200
|
+
if (!t.contReported) {
|
|
201
|
+
consume();
|
|
202
|
+
t.contReported = matches[0];
|
|
203
|
+
try {
|
|
204
|
+
outbound.push(await buildMapping(p, key, matches[0], when, ent));
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
t.contMappingDue = { prior: matches[0], kind: 'report' };
|
|
208
|
+
outboundError = String(err?.message ?? err);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else
|
|
212
|
+
consume();
|
|
213
|
+
}
|
|
214
|
+
return { handled: true, matches: matches.length, chained, name: chained ? t.name : undefined, outbound, outboundError };
|
|
215
|
+
}
|
|
216
|
+
async function handleMapping(p, { key, t }, doc, when, ent) {
|
|
217
|
+
if (!C.shaped(doc, { mac1: 'string', mac2: 'string', prior: 'string', next: 'string', to: 'string' }))
|
|
218
|
+
return { handled: true, error: 'malformed mapping' };
|
|
219
|
+
const { mac1, mac2, ...body } = doc;
|
|
220
|
+
const fail = (why) => { say(p, `Kontinuitäts-Mapping verworfen: ${why}`); return { handled: true, error: why }; };
|
|
221
|
+
if (body.to !== t.channel.own.anchor)
|
|
222
|
+
return fail('to ≠ eigener neuer Anker');
|
|
223
|
+
if (body.next !== key)
|
|
224
|
+
return fail('next ≠ neuer Anker der Gegenseite');
|
|
225
|
+
// prior MUSS im Schnappschuss liegen (nie das frische Tupel, nie tiefere Kettenglieder)
|
|
226
|
+
if (!(t.priorCands?.counterpart ?? []).includes(body.prior))
|
|
227
|
+
return fail('prior nicht im Prior-Candidate-Set');
|
|
228
|
+
const old = p.contacts.get(body.prior);
|
|
229
|
+
if (!old?.channel?.own)
|
|
230
|
+
return fail('alte Beziehung ohne eigenen Kontext');
|
|
231
|
+
const msg = jcs(body);
|
|
232
|
+
if ((await hmac(await kMac(old.channel.own.x.priv, old.channel.counterpartKa, 'rltp/visibility/mac/cont1'), msg)) !== mac1)
|
|
233
|
+
return fail('mac1 (alter Beziehungs-Schlüssel)');
|
|
234
|
+
if ((await hmac(await kMac(t.channel.own.x.priv, t.channel.counterpartKa, 'rltp/visibility/mac/cont2'), msg)) !== mac2)
|
|
235
|
+
return fail('mac2 (neuer Beziehungs-Schlüssel)');
|
|
236
|
+
const senderIsRecord = key < t.channel.own.anchor;
|
|
237
|
+
const outbound = [];
|
|
238
|
+
if (senderIsRecord) {
|
|
239
|
+
// Record-Freeze: abweichendes prior derselben (next,to) = Äquivokation
|
|
240
|
+
if (t.contRecordPrior && t.contRecordPrior !== body.prior)
|
|
241
|
+
return fail('Äquivokation — Record-Wahl ist eingefroren');
|
|
242
|
+
// fehlbare Arbeit VOR der Mutation (B-2): erst das Alignment-Mapping
|
|
243
|
+
// bauen, dann Freeze + Kette in einem synchronen Zug
|
|
244
|
+
t.contRecordPrior = body.prior;
|
|
245
|
+
chainTuple(p, key, body.prior);
|
|
246
|
+
if (!t.contAligned) {
|
|
247
|
+
try {
|
|
248
|
+
const mapping = await buildMapping(p, key, body.prior, when, ent);
|
|
249
|
+
t.contAligned = true;
|
|
250
|
+
if (t.contMappingDue?.prior === body.prior)
|
|
251
|
+
delete t.contMappingDue;
|
|
252
|
+
outbound.push(mapping);
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
t.contMappingDue = { prior: body.prior, kind: 'align' };
|
|
256
|
+
return { handled: true, chained: p.contacts.get(body.prior)?.deactivated === true, name: t.name, outbound, outboundError: String(err?.message ?? err) };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// Match-Report der Nicht-Record-Seite: darf die Wahl INFORMIEREN, kettet nie
|
|
262
|
+
if (iAmRecord(t, key) && !t.contChosen) {
|
|
263
|
+
const mapping = await buildMapping(p, key, body.prior, when, ent);
|
|
264
|
+
t.contChosen = body.prior;
|
|
265
|
+
chainTuple(p, key, body.prior);
|
|
266
|
+
outbound.push(mapping);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { handled: true, chained: p.contacts.get(body.prior)?.deactivated === true, name: t.name, outbound };
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The host's producing call for a mapping the receiver still owes
|
|
273
|
+
* (review 6, B-3): a transient seal failure during probe/mapping
|
|
274
|
+
* handling persists the obligation in contMappingDue; this call — not a
|
|
275
|
+
* redelivery, which the freshness rule forbids — builds and returns it.
|
|
276
|
+
*/
|
|
277
|
+
export async function flushContinuity(p, newKey, when, ent = {}) {
|
|
278
|
+
const t = p.contacts.get(newKey);
|
|
279
|
+
if (!t?.contMappingDue)
|
|
280
|
+
return { outbound: [] };
|
|
281
|
+
// Single-Flight (Review 7, B-3): zwei parallele Flushes liefern nie
|
|
282
|
+
// zwei Mappings für EINE Pflicht
|
|
283
|
+
if (t.contFlushPending)
|
|
284
|
+
return { outbound: [], pending: true };
|
|
285
|
+
t.contFlushPending = true;
|
|
286
|
+
try {
|
|
287
|
+
const due = t.contMappingDue;
|
|
288
|
+
const mapping = await buildMapping(p, newKey, due.prior, when, ent);
|
|
289
|
+
// der Flush stellt GENAU den Zustand her, den der gelungene
|
|
290
|
+
// Direktpfad hinterlassen hätte — kind-bewusst. Und er löscht NUR
|
|
291
|
+
// die Pflicht, die er selbst gebaut hat: ist während des await eine
|
|
292
|
+
// NEUERE an ihre Stelle getreten, bleibt sie stehen (Review 8, B-3)
|
|
293
|
+
if (due.kind === 'align')
|
|
294
|
+
t.contAligned = true;
|
|
295
|
+
if (t.contMappingDue === due)
|
|
296
|
+
delete t.contMappingDue;
|
|
297
|
+
return { outbound: [mapping] };
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
t.contFlushPending = false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=continuity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuity.js","sourceRoot":"","sources":["../../src/probe/continuity.ts"],"names":[],"mappings":"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","sourcesContent":["// continuity.mjs — rltp-visibility §6a im Simulator: continuity-probe@1 +\n// continuity-mapping@1. „Kontinuität wird NACH der Zeremonie aufgelöst,\n// nie in ihr\" (Encounter 4.4): jedes Enactment UND jede Vorstellung prägt\n// ein frisches Tupel; ob es ein Wiedersehen war, klärt die Probe über den\n// frischen Kanal — automatisch, ohne Dialog, ohne Namen.\n//\n// Die Probe ist das dritte Mitglied EINER Familie (Stern, Mapping, Probe:\n// geblendete Werte unter Beziehungs-Schlüsseln): die EIGENEN bisherigen\n// pair-Anker reisen als HMAC unter k_p (DH des NEUEN Kanals), gepolstert\n// auf Vielfache von 256, sortiert, sequenziert. Nur wer eine der\n// Beziehungen wirklich hält, kann matchen; ein leerer Bestand sendet\n// trotzdem (sonst leakte die Abwesenheit die Kardinalität „null\").\n//\n// Die Maschine (6a.4): EIN Wähler (Record-Seite = lexikographisch\n// kleinerer neuer pair-Anker), EINE endgültige Wahl (eingefroren,\n// Abweichung = Äquivokation), EIN Auslöser (Record-Seite kettet atomar\n// mit ihrer Wahl; die Gegenseite NUR auf deren verifiziertes Mapping —\n// ihr eigenes ist ein Match-Report). Kette: neues Tupel wird Kopf, das\n// alte deaktiviert; Evidenz akkumuliert AUF DER BEZIEHUNG (✓ bleibt ✓),\n// für Dritte unsichtbar. Wer die Probe nicht beantworten kann, IST\n// protokollisch eine neue Beziehung (Identity 9.3).\nimport { jcs } from '../core.js'\nimport * as C from './deps.js'\nimport type { Person } from './deps.js'\nimport { hmac } from './trust.js'\n\nconst say = (p: Person, m: string) => p.log.push(m)\nconst CHUNK = 256\n\n// ── Schlüssel (Spec-Infostrings, richtungsgebunden) ─────────────────────\n// k_p = HKDF(ECDH(newPairX_sender, newPairX_recipient),\n// 'rltp/visibility/blind/probe/' + senderNewPair + '/' + recipientNewPair)\nconst kProbe = async (xPriv: CryptoKey, theirKa: string, senderAnchor: string, recipientAnchor: string) =>\n C.hkdf(await C.ecdh(xPriv, C.xRawOfMk(theirKa)!), 'rltp/visibility/blind/probe/' + senderAnchor + '/' + recipientAnchor)\nconst kMac = async (xPriv: CryptoKey, theirKa: string, info: string) =>\n C.hkdf(await C.ecdh(xPriv, C.xRawOfMk(theirKa)!), info)\n\n// ── Schnappschuss-Helfer (Prior-Candidate-Set, Sicht des Halters) ───────\n// wird von introduce.mjs BEI TUPEL-ERZEUGUNG aufgerufen: aktive Heads,\n// das frische Tupel selbst ausgeschlossen; deaktivierte Glieder nie.\nexport const snapshotPriors = (p: Person) => ({\n own: [...p.contacts.values()].filter((c) => !c.deactivated && c.channel?.own).map((c) => c.channel.own.anchor),\n counterpart: [...p.contacts.entries()].filter(([, c]) => !c.deactivated).map(([k]) => k),\n})\n\n// ── continuity-probe@1: bauen ───────────────────────────────────────────\nexport async function buildProbe (p: Person, newKey: string, ent: any = {}) {\n const t = p.contacts.get(newKey)\n if (!t?.channel?.own) return null\n t.probeSeqOut = (t.probeSeqOut ?? 0) + 1\n const kp = await kProbe(t.channel.own.x.priv, t.channel.counterpartKa, t.channel.own.anchor, newKey)\n const entries = []\n for (const a of t.priorCands?.own ?? []) entries.push(await hmac(kp, a))\n // Polstern auf das nächste Vielfache von 256 (leerer Bestand → 1 Voll-Polster-Chunk)\n const total = Math.max(CHUNK, Math.ceil(entries.length / CHUNK) * CHUNK)\n while (entries.length < total) entries.push(C.b64uOf(ent.pad ?? C.rand(32)))\n entries.sort()\n const outbound = []\n for (let i = 0; i * CHUNK < entries.length; i++) {\n const body = { type: 'continuity-probe@1', probe: String(t.probeSeqOut), seq: String(i + 1), last: (i + 1) * CHUNK >= entries.length, blinded: entries.slice(i * CHUNK, (i + 1) * CHUNK) }\n outbound.push({ to: t, kind: 'continuity-probe@1 (geblendet, gepolstert, versiegelt)', env: await C.seal({ ...body, mac: await hmac(kp, jcs(body)) }, t.channel.counterpartKa, ent) })\n }\n say(p, `Kontinuitäts-Probe an ${t.name}: ${t.priorCands?.own.length ?? 0} eigene Beziehungs-Anker, geblendet + gepolstert (${entries.length})`)\n return outbound\n}\n\n// ── continuity-mapping@1: bauen ─────────────────────────────────────────\n// prior = EIGENER alter pair-Anker · next = eigener neuer · to = neuer der\n// Gegenseite; mac1 unter dem ALTEN Beziehungs-Schlüssel, mac2 unter dem\n// NEUEN — nur derselbe Schlüssel-Inhaber kann beide (für die Adressatin\n// fälschbar: Klasse V, abstreitbar).\nasync function buildMapping (p: Person, newKey: string, oldKey: string, when: number, ent: any = {}) {\n const t = p.contacts.get(newKey), old = p.contacts.get(oldKey)\n const body = { type: 'continuity-mapping@1', prior: old.channel.own.anchor, next: t.channel.own.anchor, to: newKey, revision: '1', issuedAt: C.iso(when) }\n const msg = jcs(body)\n const doc = { ...body,\n mac1: await hmac(await kMac(old.channel.own.x.priv, old.channel.counterpartKa, 'rltp/visibility/mac/cont1'), msg),\n mac2: await hmac(await kMac(t.channel.own.x.priv, t.channel.counterpartKa, 'rltp/visibility/mac/cont2'), msg) }\n return { to: t, kind: 'continuity-mapping@1 (mac1 alt · mac2 neu, versiegelt)', env: await C.seal(doc, t.channel.counterpartKa, ent) }\n}\n\n// ── die Kette: neues Tupel wird Kopf, das alte deaktiviert ──────────────\n// Tupel-Ebene bleibt (Stern-Salts, Probe-Sequenzen); KETTEN-Ebene wandert:\n// Kontakt-Gedächtnis, Evidenz (✓ akkumuliert), Offenlegungen, Gruppen.\nfunction chainTuple (p: Person, newKey: string, oldKey: string) {\n const head = p.contacts.get(newKey), old = p.contacts.get(oldKey)\n if (!head || !old || old.deactivated) return false // idempotent\n old.deactivated = true\n old.chainedInto = newKey\n head.chain = [...(old.chain ?? []), oldKey]\n if (old.state === '✓' || head.state === '✓') { head.state = '✓'; head.provenance = 'ceremony' }\n else if (old.state === '⇄' || head.state === '⇄') head.state = '⇄'\n head.since = [old.since, head.since].filter(Boolean).sort()[0]\n head.name = old.name // lokales Kontakt-Gedächtnis\n head.selfAnchor ??= old.selfAnchor; head.mapping ??= old.mapping\n head.trustGiven ??= old.trustGiven; head.trustReceived ??= old.trustReceived\n head.trustPaused ??= old.trustPaused; head.sentMapping ??= old.sentMapping\n head.starReceived ??= old.starReceived; head.starInfo ??= old.starInfo\n head.sharedGroups = [...new Set([...(old.sharedGroups ?? []), ...(head.sharedGroups ?? [])])]\n say(p, `Kontinuität: ${head.name} wiedererkannt — neues Tupel an die Beziehung gekettet (${head.state}, ${head.chain.length + 1} Enactments)`)\n return true\n}\n\n// ── Empfangs-Dispatch ───────────────────────────────────────────────────\nconst iAmRecord = (t: any, newKey: string) => t.channel.own.anchor < newKey // lexikographisch kleinerer NEUER Anker\nconst tupleOf = (p: Person, rkid: string) => {\n const ctx = p.contexts.get(rkid)\n if (!ctx) return null\n const e = [...p.contacts.entries()].find(([, c]) => c.channel?.own?.anchor === ctx.anchor)\n return e ? { key: e[0], t: e[1], ctx } : null\n}\n\nexport async function receiveContinuity (p: Person, env: any, when: number, ent: any = {}) {\n const opened = await C.openEnvelope(p, env) // Stufen 1–4, Cache-Lesung\n if (opened.duplicate) return { handled: true, duplicate: true }\n if (opened.error) return { handled: false }\n const r = await receiveContinuityInner(p, env, opened.doc, when, ent)\n if (r?.handled && !(r as any).error) C.effectDone(p, opened.digest)\n return r\n}\nasync function receiveContinuityInner (p: Person, env: any, doc: any, when: number, ent: any = {}) {\n const hit = tupleOf(p, env.rkid)\n if (!hit) return { handled: false }\n if (doc?.type === 'continuity-probe@1') return handleProbe(p, hit, doc, when, ent)\n if (doc?.type === 'continuity-mapping@1') return handleMapping(p, hit, doc, when, ent)\n return { handled: false, doc }\n}\n\nasync function handleProbe (p: Person, { key, t }: any, doc: any, when: number, ent: any) {\n // form BEFORE fields (M-3): mac, sequence and chunk list as used below\n if (!C.shaped(doc, { mac: 'string', probe: 'string', seq: 'string', blinded: 'array' }) || typeof doc.last !== 'boolean'\n || !C.intStr(doc.probe) || !C.intStr(doc.seq) // NaN in probeSeqIn wäre Replay-Zustandskorruption (Review 9, P-B1)\n || doc.blinded.length !== 256 // die Chunkform ist FEST: genau 256 Einträge (Review 10, P-B1-Rest)\n || !doc.blinded.every((b: any) => typeof b === 'string')) return { handled: true, error: 'malformed probe' }\n // Senderseitiger k_p, aus Empfängersicht nachgerechnet (Richtung!)\n const kp = await kProbe(t.channel.own.x.priv, t.channel.counterpartKa, key, t.channel.own.anchor)\n const { mac, ...body } = doc\n if ((await hmac(kp, jcs(body))) !== mac) return { handled: true, error: 'probe mac' }\n if (BigInt(doc.probe) <= BigInt(t.probeSeqIn ?? '0')) return { handled: true, error: 'probe replay (nicht streng größer)' }\n // Chunk-Sammlung pro (Tupel, probe); höhere probe verwirft die offene\n if (t.probeAsm?.probe !== doc.probe) t.probeAsm = { probe: doc.probe, blinded: [], next: 1 }\n if (Number(doc.seq) !== t.probeAsm.next) return { handled: true, error: 'malformed probe (chunk order)' }\n t.probeAsm.next += 1\n t.probeAsm.blinded.push(...doc.blinded)\n if (!doc.last) return { handled: true, partial: true }\n // finaler Chunk: union lesen, dann vollzieht sich der EFFEKT (consume\n // + Wahl + Kette) VOR dem fehlbaren Mapping-Bau — bewusst, denn die\n // Frische-Regel verbietet Chunk-Replays als Retry-Weg; ein Bau-Fehler\n // wird als getypte Pflicht persistiert und über flushContinuity()\n // eingelöst (Reviews 5–8)\n const union = t.probeAsm.blinded\n const consume = () => { t.probeAsm = null; t.probeSeqIn = doc.probe } // als STRING — BigInt-Vergleich, 18 Stellen exakt\n // Schnitt gegen die GEGENSEITEN-Anker des eigenen Prior-Candidate-Sets\n const matches = []\n for (const cp of t.priorCands?.counterpart ?? []) {\n if (union.includes(await hmac(kp, cp))) matches.push(cp)\n }\n if (!matches.length) { consume(); say(p, `Probe von ${t.name}: kein Match — ehrlich eine neue Beziehung`); return { handled: true, matches: 0 } }\n const outbound = []\n let chained = false\n let outboundError\n if (iAmRecord(t, key)) {\n // Record-Seite: einmal wählen, endgültig; Kette atomar mit der Wahl\n if (!t.contChosen) {\n const choice = matches[0] // Wahl frei — hier: erster Match (reichste Historie wäre App-Sache)\n consume()\n t.contChosen = choice\n chained = chainTuple(p, key, choice)\n try { outbound.push(await buildMapping(p, key, choice, when, ent)) }\n catch (err: any) { t.contMappingDue = { prior: choice, kind: 'choice' }; outboundError = String(err?.message ?? err) }\n } else consume()\n } else {\n // Nicht-Record: Match-Report (löst NIE eine Kette aus)\n if (!t.contReported) {\n consume()\n t.contReported = matches[0]\n try { outbound.push(await buildMapping(p, key, matches[0], when, ent)) }\n catch (err: any) { t.contMappingDue = { prior: matches[0], kind: 'report' }; outboundError = String(err?.message ?? err) }\n } else consume()\n }\n return { handled: true, matches: matches.length, chained, name: chained ? t.name : undefined, outbound, outboundError }\n}\n\nasync function handleMapping (p: Person, { key, t }: any, doc: any, when: number, ent: any) {\n if (!C.shaped(doc, { mac1: 'string', mac2: 'string', prior: 'string', next: 'string', to: 'string' })) return { handled: true, error: 'malformed mapping' }\n const { mac1, mac2, ...body } = doc\n const fail = (why: string) => { say(p, `Kontinuitäts-Mapping verworfen: ${why}`); return { handled: true, error: why } }\n if (body.to !== t.channel.own.anchor) return fail('to ≠ eigener neuer Anker')\n if (body.next !== key) return fail('next ≠ neuer Anker der Gegenseite')\n // prior MUSS im Schnappschuss liegen (nie das frische Tupel, nie tiefere Kettenglieder)\n if (!(t.priorCands?.counterpart ?? []).includes(body.prior)) return fail('prior nicht im Prior-Candidate-Set')\n const old = p.contacts.get(body.prior)\n if (!old?.channel?.own) return fail('alte Beziehung ohne eigenen Kontext')\n const msg = jcs(body)\n if ((await hmac(await kMac(old.channel.own.x.priv, old.channel.counterpartKa, 'rltp/visibility/mac/cont1'), msg)) !== mac1) return fail('mac1 (alter Beziehungs-Schlüssel)')\n if ((await hmac(await kMac(t.channel.own.x.priv, t.channel.counterpartKa, 'rltp/visibility/mac/cont2'), msg)) !== mac2) return fail('mac2 (neuer Beziehungs-Schlüssel)')\n const senderIsRecord = key < t.channel.own.anchor\n const outbound = []\n if (senderIsRecord) {\n // Record-Freeze: abweichendes prior derselben (next,to) = Äquivokation\n if (t.contRecordPrior && t.contRecordPrior !== body.prior) return fail('Äquivokation — Record-Wahl ist eingefroren')\n // fehlbare Arbeit VOR der Mutation (B-2): erst das Alignment-Mapping\n // bauen, dann Freeze + Kette in einem synchronen Zug\n t.contRecordPrior = body.prior\n chainTuple(p, key, body.prior)\n if (!t.contAligned) {\n try { const mapping = await buildMapping(p, key, body.prior, when, ent); t.contAligned = true; if (t.contMappingDue?.prior === body.prior) delete t.contMappingDue; outbound.push(mapping) }\n catch (err: any) { t.contMappingDue = { prior: body.prior, kind: 'align' }; return { handled: true, chained: p.contacts.get(body.prior)?.deactivated === true, name: t.name, outbound, outboundError: String(err?.message ?? err) } }\n }\n } else {\n // Match-Report der Nicht-Record-Seite: darf die Wahl INFORMIEREN, kettet nie\n if (iAmRecord(t, key) && !t.contChosen) {\n const mapping = await buildMapping(p, key, body.prior, when, ent)\n t.contChosen = body.prior\n chainTuple(p, key, body.prior)\n outbound.push(mapping)\n }\n }\n return { handled: true, chained: p.contacts.get(body.prior)?.deactivated === true, name: t.name, outbound }\n}\n\n/**\n * The host's producing call for a mapping the receiver still owes\n * (review 6, B-3): a transient seal failure during probe/mapping\n * handling persists the obligation in contMappingDue; this call — not a\n * redelivery, which the freshness rule forbids — builds and returns it.\n */\nexport async function flushContinuity (p: Person, newKey: string, when: number, ent: any = {}) {\n const t = p.contacts.get(newKey)\n if (!t?.contMappingDue) return { outbound: [] }\n // Single-Flight (Review 7, B-3): zwei parallele Flushes liefern nie\n // zwei Mappings für EINE Pflicht\n if (t.contFlushPending) return { outbound: [], pending: true }\n t.contFlushPending = true\n try {\n const due = t.contMappingDue\n const mapping = await buildMapping(p, newKey, due.prior, when, ent)\n // der Flush stellt GENAU den Zustand her, den der gelungene\n // Direktpfad hinterlassen hätte — kind-bewusst. Und er löscht NUR\n // die Pflicht, die er selbst gebaut hat: ist während des await eine\n // NEUERE an ihre Stelle getreten, bleibt sie stehen (Review 8, B-3)\n if (due.kind === 'align') t.contAligned = true\n if (t.contMappingDue === due) delete t.contMappingDue\n return { outbound: [mapping] }\n } finally { t.contFlushPending = false }\n}\n"]}
|